agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v21 2/8] Row pattern recognition patch (parse/analysis).
381+ messages / 2 participants
[nested] [flat]

* [PATCH v21 2/8] Row pattern recognition patch (parse/analysis).
@ 2024-08-26 04:32 Tatsuo Ishii <ishii@postgresql.org>
  0 siblings, 0 replies; 381+ messages in thread

From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw)

---
 src/backend/parser/parse_agg.c    |   7 +
 src/backend/parser/parse_clause.c | 296 +++++++++++++++++++++++++++++-
 src/backend/parser/parse_expr.c   |   6 +
 src/backend/parser/parse_func.c   |   3 +
 4 files changed, 311 insertions(+), 1 deletion(-)

diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bee7d8346a..9bc22a836a 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -577,6 +577,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
 			errkind = true;
 			break;
 
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
+
 			/*
 			 * There is intentionally no default: case here, so that the
 			 * compiler will warn if we add a new ParseExprKind without
@@ -967,6 +971,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 8118036495..9762dce81f 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -98,7 +98,14 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
-
+static void transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+						 List **targetlist);
+static List *transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+								   List **targetlist);
+static void transformPatternClause(ParseState *pstate, WindowClause *wc,
+								   WindowDef *windef);
+static List *transformMeasureClause(ParseState *pstate, WindowClause *wc,
+									WindowDef *windef);
 
 /*
  * transformFromClause -
@@ -2956,6 +2963,10 @@ transformWindowDefinitions(ParseState *pstate,
 											 rangeopfamily, rangeopcintype,
 											 &wc->endInRangeFunc,
 											 windef->endOffset);
+
+		/* Process Row Pattern Recognition related clauses */
+		transformRPR(pstate, wc, windef, targetlist);
+
 		wc->winref = winref;
 
 		result = lappend(result, wc);
@@ -3820,3 +3831,286 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * transformRPR
+ *		Process Row Pattern Recognition related clauses
+ */
+static void
+transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+			 List **targetlist)
+{
+	/*
+	 * Window definition exists?
+	 */
+	if (windef == NULL)
+		return;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	/* Check Frame option. Frame must start at current row */
+	if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("FRAME must start at current row when row patttern recognition is used")));
+
+	/* Transform AFTER MACH SKIP TO clause */
+	wc->rpSkipTo = windef->rpCommonSyntax->rpSkipTo;
+
+	/* Transform AFTER MACH SKIP TO variable */
+	wc->rpSkipVariable = windef->rpCommonSyntax->rpSkipVariable;
+
+	/* Transform SEEK or INITIAL clause */
+	wc->initial = windef->rpCommonSyntax->initial;
+
+	/* Transform DEFINE clause into list of TargetEntry's */
+	wc->defineClause = transformDefineClause(pstate, wc, windef, targetlist);
+
+	/* Check PATTERN clause and copy to patternClause */
+	transformPatternClause(pstate, wc, windef);
+
+	/* Transform MEASURE clause */
+	transformMeasureClause(pstate, wc, windef);
+}
+
+/*
+ * transformDefineClause Process DEFINE clause and transform ResTarget into
+ *		list of TargetEntry.
+ *
+ * XXX we only support column reference in row pattern definition search
+ * condition, e.g. "price". <row pattern definition variable name>.<column
+ * reference> is not supported, e.g. "A.price".
+ */
+static List *
+transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef,
+					  List **targetlist)
+{
+	/* DEFINE variable name initials */
+	static char *defineVariableInitials = "abcdefghijklmnopqrstuvwxyz";
+
+	ListCell   *lc,
+			   *l;
+	ResTarget  *restarget,
+			   *r;
+	List	   *restargets;
+	List	   *defineClause;
+	char	   *name;
+	int			initialLen;
+	int			i;
+
+	/*
+	 * If Row Definition Common Syntax exists, DEFINE clause must exist. (the
+	 * raw parser should have already checked it.)
+	 */
+	Assert(windef->rpCommonSyntax->rpDefs != NULL);
+
+	/*
+	 * Check and add "A AS A IS TRUE" if pattern variable is missing in DEFINE
+	 * per the SQL standard.
+	 */
+	restargets = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpPatterns)
+	{
+		A_Expr	   *a;
+		bool		found = false;
+
+		if (!IsA(lfirst(lc), A_Expr))
+			ereport(ERROR,
+					errmsg("node type is not A_Expr"));
+
+		a = (A_Expr *) lfirst(lc);
+		name = strVal(a->lexpr);
+
+		foreach(l, windef->rpCommonSyntax->rpDefs)
+		{
+			restarget = (ResTarget *) lfirst(l);
+
+			if (!strcmp(restarget->name, name))
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			/*
+			 * "name" is missing. So create "name AS name IS TRUE" ResTarget
+			 * node and add it to the temporary list.
+			 */
+			A_Const    *n;
+
+			restarget = makeNode(ResTarget);
+			n = makeNode(A_Const);
+			n->val.boolval.type = T_Boolean;
+			n->val.boolval.boolval = true;
+			n->location = -1;
+			restarget->name = pstrdup(name);
+			restarget->indirection = NIL;
+			restarget->val = (Node *) n;
+			restarget->location = -1;
+			restargets = lappend((List *) restargets, restarget);
+		}
+	}
+
+	if (list_length(restargets) >= 1)
+	{
+		/* add missing DEFINEs */
+		windef->rpCommonSyntax->rpDefs =
+			list_concat(windef->rpCommonSyntax->rpDefs, restargets);
+		list_free(restargets);
+	}
+
+	/*
+	 * Check for duplicate row pattern definition variables.  The standard
+	 * requires that no two row pattern definition variable names shall be
+	 * equivalent.
+	 */
+	restargets = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		restarget = (ResTarget *) lfirst(lc);
+		name = restarget->name;
+
+		/*
+		 * Add DEFINE expression (Restarget->val) to the targetlist as a
+		 * TargetEntry if it does not exist yet. Planner will add the column
+		 * ref var node to the outer plan's target list later on. This makes
+		 * DEFINE expression could access the outer tuple while evaluating
+		 * PATTERN.
+		 *
+		 * XXX: adding whole expressions of DEFINE to the plan.targetlist is
+		 * not so good, because it's not necessary to evalute the expression
+		 * in the target list while running the plan. We should extract the
+		 * var nodes only then add them to the plan.targetlist.
+		 */
+		findTargetlistEntrySQL99(pstate, (Node *) restarget->val,
+								 targetlist, EXPR_KIND_RPR_DEFINE);
+
+		/*
+		 * Make sure that the row pattern definition search condition is a
+		 * boolean expression.
+		 */
+		transformWhereClause(pstate, restarget->val,
+							 EXPR_KIND_RPR_DEFINE, "DEFINE");
+
+		foreach(l, restargets)
+		{
+			char	   *n;
+
+			r = (ResTarget *) lfirst(l);
+			n = r->name;
+
+			if (!strcmp(n, name))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("row pattern definition variable name \"%s\" appears more than once in DEFINE clause",
+								name),
+						 parser_errposition(pstate, exprLocation((Node *) r))));
+		}
+		restargets = lappend(restargets, restarget);
+	}
+	list_free(restargets);
+
+	/*
+	 * Create list of row pattern DEFINE variable name's initial. We assign
+	 * [a-z] to them (up to 26 variable names are allowed).
+	 */
+	restargets = NIL;
+	i = 0;
+	initialLen = strlen(defineVariableInitials);
+
+	foreach(lc, windef->rpCommonSyntax->rpDefs)
+	{
+		char		initial[2];
+
+		restarget = (ResTarget *) lfirst(lc);
+		name = restarget->name;
+
+		if (i >= initialLen)
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("number of row pattern definition variable names exceeds %d",
+							initialLen),
+					 parser_errposition(pstate,
+										exprLocation((Node *) restarget))));
+		}
+		initial[0] = defineVariableInitials[i++];
+		initial[1] = '\0';
+		wc->defineInitial = lappend(wc->defineInitial,
+									makeString(pstrdup(initial)));
+	}
+
+	defineClause = transformTargetList(pstate, windef->rpCommonSyntax->rpDefs,
+									   EXPR_KIND_RPR_DEFINE);
+
+	/* mark column origins */
+	markTargetListOrigins(pstate, defineClause);
+
+	/* mark all nodes in the DEFINE clause tree with collation information */
+	assign_expr_collations(pstate, (Node *) defineClause);
+
+	return defineClause;
+}
+
+/*
+ * transformPatternClause
+ *		Process PATTERN clause and return PATTERN clause in the raw parse tree
+ */
+static void
+transformPatternClause(ParseState *pstate, WindowClause *wc,
+					   WindowDef *windef)
+{
+	ListCell   *lc;
+
+	/*
+	 * Row Pattern Common Syntax clause exists?
+	 */
+	if (windef->rpCommonSyntax == NULL)
+		return;
+
+	wc->patternVariable = NIL;
+	wc->patternRegexp = NIL;
+	foreach(lc, windef->rpCommonSyntax->rpPatterns)
+	{
+		A_Expr	   *a;
+		char	   *name;
+		char	   *regexp;
+
+		if (!IsA(lfirst(lc), A_Expr))
+			ereport(ERROR,
+					errmsg("node type is not A_Expr"));
+
+		a = (A_Expr *) lfirst(lc);
+		name = strVal(a->lexpr);
+
+		wc->patternVariable = lappend(wc->patternVariable, makeString(pstrdup(name)));
+		regexp = strVal(lfirst(list_head(a->name)));
+
+		wc->patternRegexp = lappend(wc->patternRegexp, makeString(pstrdup(regexp)));
+	}
+}
+
+/*
+ * transformMeasureClause
+ *		Process MEASURE clause
+ *	XXX MEASURE clause is not supported yet
+ */
+static List *
+transformMeasureClause(ParseState *pstate, WindowClause *wc,
+					   WindowDef *windef)
+{
+	if (windef->rowPatternMeasures == NIL)
+		return NIL;
+
+	ereport(ERROR,
+			(errcode(ERRCODE_SYNTAX_ERROR),
+			 errmsg("%s", "MEASURE clause is not supported yet"),
+			 parser_errposition(pstate, exprLocation((Node *) windef->rowPatternMeasures))));
+	return NIL;
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 56e413da9f..c187b3278d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -577,6 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		case EXPR_KIND_COPY_WHERE:
 		case EXPR_KIND_GENERATED_COLUMN:
 		case EXPR_KIND_CYCLE_MARK:
+		case EXPR_KIND_RPR_DEFINE:
 			/* okay */
 			break;
 
@@ -1860,6 +1861,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_GENERATED_COLUMN:
 			err = _("cannot use subquery in column generation expression");
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			err = _("cannot use subquery in DEFINE expression");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -3199,6 +3203,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "GENERATED AS";
 		case EXPR_KIND_CYCLE_MARK:
 			return "CYCLE";
+		case EXPR_KIND_RPR_DEFINE:
+			return "DEFINE";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9b23344a3b..4c482abb30 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2658,6 +2658,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
 		case EXPR_KIND_CYCLE_MARK:
 			errkind = true;
 			break;
+		case EXPR_KIND_RPR_DEFINE:
+			errkind = true;
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
-- 
2.25.1


----Next_Part(Mon_Aug_26_13_39_47_2024_878)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v21-0003-Row-pattern-recognition-patch-rewriter.patch"



^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread

* [PATCH] Reproduce filtering issue.
@ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at>
  0 siblings, 0 replies; 381+ messages in thread

From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw)

---
 contrib/test_decoding/expected/filtering.out |  74 ++++++++++++++
 contrib/test_decoding/specs/filtering.spec   | 101 +++++++++++++++++++
 src/backend/executor/nodeModifyTable.c       |   2 +
 src/backend/replication/logical/snapbuild.c  |   3 +
 src/test/isolation/isolationtester.c         |   9 +-
 5 files changed, 188 insertions(+), 1 deletion(-)
 create mode 100644 contrib/test_decoding/expected/filtering.out
 create mode 100644 contrib/test_decoding/specs/filtering.spec

diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out
new file mode 100644
index 00000000000..6ba9690509f
--- /dev/null
+++ b/contrib/test_decoding/expected/filtering.out
@@ -0,0 +1,74 @@
+Parsed test spec with 5 sessions
+
+starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step s1_assign_xid: 
+    BEGIN;
+    CREATE TABLE c(i int);
+
+step s3_repack: 
+	REPACK (CONCURRENTLY) a;
+ <waiting ...>
+step s2_assign_xid: 
+    BEGIN;
+    CREATE TABLE d(i int);
+
+step s1_rollback: 
+    ROLLBACK;
+
+step s4_insert: 
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+ <waiting ...>
+step s5_wakeup_snapbuild: 
+	SELECT injection_points_wakeup('snapbuild-full');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s2_rollback: 
+    ROLLBACK;
+
+step s5_wakeup_insert_speculative: 
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s4_insert: <... completed>
+step s4_insert_commit: 
+	COMMIT;
+
+step s5_wakeup_repack: 
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step s3_repack: <... completed>
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec
new file mode 100644
index 00000000000..a02c9f8facb
--- /dev/null
+++ b/contrib/test_decoding/specs/filtering.spec
@@ -0,0 +1,101 @@
+setup
+{
+	CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off);
+	INSERT INTO a(i, j) VALUES (1, 1), (2, 2);
+	CREATE TABLE t(i int primary key);
+	INSERT INTO t(i) VALUES (1);
+	CREATE EXTENSION injection_points;
+}
+
+session s1
+step s1_assign_xid
+{
+    BEGIN;
+    CREATE TABLE c(i int);
+}
+step s1_rollback
+{
+    ROLLBACK;
+}
+
+session s2
+step s2_assign_xid
+{
+    BEGIN;
+    CREATE TABLE d(i int);
+}
+step s2_rollback
+{
+    ROLLBACK;
+}
+
+session s3
+setup
+{
+	SELECT injection_points_attach('snapbuild-full', 'wait');
+	SELECT injection_points_attach('repack-concurrently-before-lock', 'wait');
+}
+step s3_repack
+{
+	REPACK (CONCURRENTLY) a;
+}
+teardown
+{
+	SELECT injection_points_detach('repack-concurrently-before-lock');
+	SELECT injection_points_detach('snapbuild-full');
+}
+
+session s4
+setup
+{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('insert-speculative-before-confirm', 'wait');
+}
+step s4_insert
+{
+	BEGIN;
+	INSERT INTO t(i)
+	SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i;
+}
+step s4_insert_commit
+{
+	COMMIT;
+}
+teardown
+{
+	SELECT injection_points_detach('insert-speculative-before-confirm');
+}
+
+session s5
+step s5_wakeup_snapbuild
+{
+	SELECT injection_points_wakeup('snapbuild-full');
+}
+step s5_wakeup_insert_speculative
+{
+	SELECT injection_points_wakeup('insert-speculative-before-confirm');
+}
+step s5_wakeup_repack
+{
+	SELECT injection_points_wakeup('repack-concurrently-before-lock');
+}
+
+permutation
+# Bring the snapshot builder to the FULL_SNAPSHOT state.
+s1_assign_xid
+s3_repack
+s2_assign_xid
+s1_rollback
+# Perform the speculative insert, but no confirmation so far. The snapshot
+# builder should decode it.
+s4_insert
+# Let the snapshout builder achieve CONSISTENT state and finish the setup.
+s5_wakeup_snapbuild
+s2_rollback
+# While REPACK is waiting on repack-concurrently-before-lock, let the insert
+# get confirmed. Relation filtering is now enabled.
+s5_wakeup_insert_speculative
+s4_insert_commit
+# REPACK should now decode the speculative insert and decode the speculative
+# insert (with the confirmation record filtered out).
+s5_wakeup_repack
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 680c29f35d5..6d5482e5746 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context,
 												   slot, arbiterIndexes,
 												   &specConflict);
 
+			INJECTION_POINT("insert-speculative-before-confirm", NULL);
+
 			/* adjust the tuple's state accordingly */
 			table_tuple_complete_speculative(resultRelationDesc, slot,
 											 specToken, !specConflict);
diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c
index fbdd4600a2b..883e5b6e18f 100644
--- a/src/backend/replication/logical/snapbuild.c
+++ b/src/backend/replication/logical/snapbuild.c
@@ -141,6 +141,7 @@
 #include "storage/procarray.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 #include "utils/snapshot.h"
@@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn
 		builder->state = SNAPBUILD_FULL_SNAPSHOT;
 		builder->next_phase_at = running->nextXid;
 
+		INJECTION_POINT("snapbuild-full", NULL);
+
 		ereport(LOG,
 				errmsg("logical decoding found initial consistent point at %X/%08X",
 					   LSN_FORMAT_ARGS(lsn)),
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..8f17ee412c9 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -216,15 +216,22 @@ main(int argc, char **argv)
 	 * exactly expect concurrent use of test tables.  However, autovacuum will
 	 * occasionally take AccessExclusiveLock to truncate a table, and we must
 	 * ignore that transient wait.
+	 *
+	 * If the session's backend is blocked, and if its background worker is
+	 * waiting on an injection point, we assume that the injection point is
+	 * the reason for the backend to be blocked. That's what we check in the
+	 * second query of the UNION. XXX Should we use a separate query for that?
 	 */
 	initPQExpBuffer(&wait_query);
 	appendPQExpBufferStr(&wait_query,
+						 "WITH blocking(res) AS ("
 						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{");
 	/* The spec syntax requires at least one session; assume that here. */
 	appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str);
 	for (i = 2; i < nconns; i++)
 		appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str);
-	appendPQExpBufferStr(&wait_query, "}')");
+	appendPQExpBufferStr(&wait_query, "}') UNION "
+						 "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking");
 
 	res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
-- 
2.47.3


--=-=-=--





^ permalink  raw  reply  [nested|flat] 381+ messages in thread


end of thread, other threads:[~2026-03-23 11:50 UTC | newest]

Thread overview: 381+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-08-26 04:32 [PATCH v21 2/8] Row pattern recognition patch (parse/analysis). Tatsuo Ishii <ishii@postgresql.org>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>
2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox