agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH] Reproduce filtering issue. 370+ messages / 2 participants [nested] [flat]
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH] Reproduce filtering issue. @ 2026-03-23 11:50 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 370+ messages in thread From: Antonin Houska @ 2026-03-23 11:50 UTC (permalink / raw) --- contrib/test_decoding/expected/filtering.out | 74 ++++++++++++++ contrib/test_decoding/specs/filtering.spec | 101 +++++++++++++++++++ src/backend/executor/nodeModifyTable.c | 2 + src/backend/replication/logical/snapbuild.c | 3 + src/test/isolation/isolationtester.c | 9 +- 5 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 contrib/test_decoding/expected/filtering.out create mode 100644 contrib/test_decoding/specs/filtering.spec diff --git a/contrib/test_decoding/expected/filtering.out b/contrib/test_decoding/expected/filtering.out new file mode 100644 index 00000000000..6ba9690509f --- /dev/null +++ b/contrib/test_decoding/expected/filtering.out @@ -0,0 +1,74 @@ +Parsed test spec with 5 sessions + +starting permutation: s1_assign_xid s3_repack s2_assign_xid s1_rollback s4_insert s5_wakeup_snapbuild s2_rollback s5_wakeup_insert_speculative s4_insert_commit s5_wakeup_repack +injection_points_attach +----------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s1_assign_xid: + BEGIN; + CREATE TABLE c(i int); + +step s3_repack: + REPACK (CONCURRENTLY) a; + <waiting ...> +step s2_assign_xid: + BEGIN; + CREATE TABLE d(i int); + +step s1_rollback: + ROLLBACK; + +step s4_insert: + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; + <waiting ...> +step s5_wakeup_snapbuild: + SELECT injection_points_wakeup('snapbuild-full'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s2_rollback: + ROLLBACK; + +step s5_wakeup_insert_speculative: + SELECT injection_points_wakeup('insert-speculative-before-confirm'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_insert: <... completed> +step s4_insert_commit: + COMMIT; + +step s5_wakeup_repack: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s3_repack: <... completed> +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/contrib/test_decoding/specs/filtering.spec b/contrib/test_decoding/specs/filtering.spec new file mode 100644 index 00000000000..a02c9f8facb --- /dev/null +++ b/contrib/test_decoding/specs/filtering.spec @@ -0,0 +1,101 @@ +setup +{ + CREATE TABLE a(i int primary key, j int) WITH (autovacuum_enabled = off); + INSERT INTO a(i, j) VALUES (1, 1), (2, 2); + CREATE TABLE t(i int primary key); + INSERT INTO t(i) VALUES (1); + CREATE EXTENSION injection_points; +} + +session s1 +step s1_assign_xid +{ + BEGIN; + CREATE TABLE c(i int); +} +step s1_rollback +{ + ROLLBACK; +} + +session s2 +step s2_assign_xid +{ + BEGIN; + CREATE TABLE d(i int); +} +step s2_rollback +{ + ROLLBACK; +} + +session s3 +setup +{ + SELECT injection_points_attach('snapbuild-full', 'wait'); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +step s3_repack +{ + REPACK (CONCURRENTLY) a; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); + SELECT injection_points_detach('snapbuild-full'); +} + +session s4 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('insert-speculative-before-confirm', 'wait'); +} +step s4_insert +{ + BEGIN; + INSERT INTO t(i) + SELECT max(i) + 1 FROM t ON CONFLICT (i) DO UPDATE SET i=EXCLUDED.i; +} +step s4_insert_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('insert-speculative-before-confirm'); +} + +session s5 +step s5_wakeup_snapbuild +{ + SELECT injection_points_wakeup('snapbuild-full'); +} +step s5_wakeup_insert_speculative +{ + SELECT injection_points_wakeup('insert-speculative-before-confirm'); +} +step s5_wakeup_repack +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +permutation +# Bring the snapshot builder to the FULL_SNAPSHOT state. +s1_assign_xid +s3_repack +s2_assign_xid +s1_rollback +# Perform the speculative insert, but no confirmation so far. The snapshot +# builder should decode it. +s4_insert +# Let the snapshout builder achieve CONSISTENT state and finish the setup. +s5_wakeup_snapbuild +s2_rollback +# While REPACK is waiting on repack-concurrently-before-lock, let the insert +# get confirmed. Relation filtering is now enabled. +s5_wakeup_insert_speculative +s4_insert_commit +# REPACK should now decode the speculative insert and decode the speculative +# insert (with the confirmation record filtered out). +s5_wakeup_repack diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 680c29f35d5..6d5482e5746 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1232,6 +1232,8 @@ ExecInsert(ModifyTableContext *context, slot, arbiterIndexes, &specConflict); + INJECTION_POINT("insert-speculative-before-confirm", NULL); + /* adjust the tuple's state accordingly */ table_tuple_complete_speculative(resultRelationDesc, slot, specToken, !specConflict); diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index fbdd4600a2b..883e5b6e18f 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -141,6 +141,7 @@ #include "storage/procarray.h" #include "storage/standby.h" #include "utils/builtins.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #include "utils/snapmgr.h" #include "utils/snapshot.h" @@ -1390,6 +1391,8 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; + INJECTION_POINT("snapbuild-full", NULL); + ereport(LOG, errmsg("logical decoding found initial consistent point at %X/%08X", LSN_FORMAT_ARGS(lsn)), diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 370+ messages in thread
* [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting @ 2026-04-03 19:01 Álvaro Herrera <alvherre@kurilemu.de> 0 siblings, 0 replies; 370+ messages in thread From: Álvaro Herrera @ 2026-04-03 19:01 UTC (permalink / raw) --- src/backend/commands/repack_worker.c | 3 ++- src/backend/replication/logical/logical.c | 8 +++++--- src/backend/replication/logical/logicalfuncs.c | 2 +- src/backend/replication/slot.c | 18 ++++++++++++------ src/backend/replication/slotfuncs.c | 11 ++++++----- src/backend/replication/walsender.c | 5 +++-- src/include/replication/logical.h | 3 ++- src/include/replication/slot.h | 2 +- 8 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index 610592a05b0..ca827223845 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -224,7 +224,7 @@ repack_setup_logical_decoding(Oid relid) * Make sure we can use logical decoding. */ CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(true); /* * A single backend should not execute multiple REPACK commands at a time, @@ -252,6 +252,7 @@ repack_setup_logical_decoding(Oid relid) ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, + true, InvalidXLogRecPtr, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f20a0fe70ad..a08aece5731 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -108,9 +108,9 @@ static void LoadOutputPlugin(OutputPluginCallbacks *callbacks, const char *plugi * decoding. */ void -CheckLogicalDecodingRequirements(void) +CheckLogicalDecodingRequirements(bool repack) { - CheckSlotRequirements(); + CheckSlotRequirements(repack); /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() @@ -305,6 +305,7 @@ StartupDecodingContext(List *output_plugin_options, * output_plugin_options -- contains options passed to the output plugin * need_full_snapshot -- if true, must obtain a snapshot able to read all * tables; if false, one that can read only catalogs is acceptable. + * for_repack -- if true, we're going to be decoding for REPACK. * restart_lsn -- if given as invalid, it's this routine's responsibility to * mark WAL as reserved by setting a convenient restart_lsn for the slot. * Otherwise, we set for decoding to start from the given LSN without @@ -325,6 +326,7 @@ LogicalDecodingContext * CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, @@ -341,7 +343,7 @@ CreateInitDecodingContext(const char *plugin, * On a standby, this check is also required while creating the slot. * Check the comments in the function. */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(for_repack); /* shorter lines... */ slot = MyReplicationSlot; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 9760818941d..512013b0ef0 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -115,7 +115,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); if (PG_ARGISNULL(0)) ereport(ERROR, diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 2c6c6773ad2..13004ed547a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -1669,19 +1669,25 @@ CheckLogicalSlotExists(void) * slots. */ void -CheckSlotRequirements(void) +CheckSlotRequirements(bool repack) { + int limit; + /* * NB: Adding a new requirement likely means that RestoreSlotFromDisk() * needs the same check. */ - /* XXX we should be able to check exactly which type of slot we need */ - if (max_replication_slots + max_repack_replication_slots == 0) + if (repack) + limit = max_repack_replication_slots; + else + limit = max_replication_slots; + + if (limit == 0) ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); if (wal_level < WAL_LEVEL_REPLICA) ereport(ERROR, diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 78dd3c4ea66..16fbd383735 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -90,7 +90,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); create_physical_replication_slot(NameStr(*name), immediately_reserve, @@ -164,6 +164,7 @@ create_logical_replication_slot(char *name, char *plugin, */ ctx = CreateInitDecodingContext(plugin, NIL, false, /* just catalogs is OK */ + false, /* not repack */ restart_lsn, XL_ROUTINE(.page_read = read_local_xlog_page, .segment_open = wal_segment_open, @@ -203,7 +204,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); create_logical_replication_slot(NameStr(*name), NameStr(*plugin), @@ -240,7 +241,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS) CheckSlotPermissions(); - CheckSlotRequirements(); + CheckSlotRequirements(false); ReplicationSlotDrop(NameStr(*name), true); @@ -648,9 +649,9 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) CheckSlotPermissions(); if (logical_slot) - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); else - CheckSlotRequirements(); + CheckSlotRequirements(false); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 75ef3419a15..9d7d675fa96 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1240,7 +1240,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(cmd->kind == REPLICATION_KIND_LOGICAL); - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); /* * Initially create persistent slot as ephemeral - that allows us to @@ -1309,6 +1309,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd) Assert(IsLogicalDecodingEnabled()); ctx = CreateInitDecodingContext(cmd->plugin, NIL, need_full_snapshot, + false, InvalidXLogRecPtr, XL_ROUTINE(.page_read = logical_read_xlog_page, .segment_open = WalSndSegmentOpen, @@ -1466,7 +1467,7 @@ StartLogicalReplication(StartReplicationCmd *cmd) QueryCompletion qc; /* make sure that our requirements are still fulfilled */ - CheckLogicalDecodingRequirements(); + CheckLogicalDecodingRequirements(false); Assert(!MyReplicationSlot); diff --git a/src/include/replication/logical.h b/src/include/replication/logical.h index bc9d4ece672..bc075b16741 100644 --- a/src/include/replication/logical.h +++ b/src/include/replication/logical.h @@ -115,11 +115,12 @@ typedef struct LogicalDecodingContext } LogicalDecodingContext; -extern void CheckLogicalDecodingRequirements(void); +extern void CheckLogicalDecodingRequirements(bool repack); extern LogicalDecodingContext *CreateInitDecodingContext(const char *plugin, List *output_plugin_options, bool need_full_snapshot, + bool for_repack, XLogRecPtr restart_lsn, XLogReaderRoutine *xl_routine, LogicalOutputPluginWriterPrepareWrite prepare_write, diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index c316a01a807..489af7d8d6c 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -378,7 +378,7 @@ extern void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname extern void StartupReplicationSlots(void); extern void CheckPointReplicationSlots(bool is_shutdown); -extern void CheckSlotRequirements(void); +extern void CheckSlotRequirements(bool repack); extern void CheckSlotPermissions(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); -- 2.47.3 --gp2pyozrd5pweboh-- ^ permalink raw reply [nested|flat] 370+ messages in thread
end of thread, other threads:[~2026-04-03 19:01 UTC | newest] Thread overview: 370+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-03-23 11:50 [PATCH] Reproduce filtering issue. Antonin Houska <ah@cybertec.at> 2026-04-03 19:01 [PATCH v52 10/10] CheckLogicalDecodingRequirements: be specific about which GUC is limiting Álvaro Herrera <alvherre@kurilemu.de>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox