From: Rui Zhao <zhaorui126@gmail.com>
Subject: [PATCH v5 3/3] Test slot creation with USE_SNAPSHOT against a commit that is not in CLOG yet
Date: Sat, 12 Sep 2026 02:26:17 +0800
Same scenario as the REPACK (CONCURRENTLY) isolation test, through the
replication protocol: CREATE_REPLICATION_SLOT ... USE_SNAPSHOT has to wait
for a transaction whose commit record it has decoded but which has not
updated CLOG yet, and the snapshot and later sessions must see that
transaction's changes.
---
src/test/recovery/meson.build | 1 +
.../recovery/t/057_snapshot_commit_race.pl | 141 ++++++++++++++++++
2 files changed, 142 insertions(+)
create mode 100644 src/test/recovery/t/057_snapshot_commit_race.pl
inline patch 1: src/test/recovery/meson.build, src/test/recovery/t/057_snapshot_commit_race.pl (+142/-0 lines) download inline-1.patchdiff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.buildindex 72113c5ac6e..ebb12dd8766 100644--- a/src/test/recovery/meson.build+++ b/src/test/recovery/meson.build@@ -65,6 +65,7 @@ tests += {
't/054_unlogged_sequence_promotion.pl',
't/055_cascade_reconnect.pl',
't/056_standby_snapshot_export.pl',
+ 't/057_snapshot_commit_race.pl',
],
},
}
diff --git a/src/test/recovery/t/057_snapshot_commit_race.pl b/src/test/recovery/t/057_snapshot_commit_race.plnew file mode 100644index 00000000000..af11ad28693--- /dev/null+++ b/src/test/recovery/t/057_snapshot_commit_race.pl@@ -0,0 +1,141 @@+# Copyright (c) 2026, PostgreSQL Global Development Group++# The snapshot of CREATE_REPLICATION_SLOT ... USE_SNAPSHOT must not be handed+# out while a transaction it takes as committed is still between writing its+# commit record and updating CLOG. Checks that the slot creation waits for+# such a transaction, and that neither the slot's snapshot nor a later+# session loses the transaction's changes.+use strict;+use warnings FATAL => 'all';++use PostgreSQL::Test::Cluster;+use PostgreSQL::Test::Utils;+use Test::More;+use Time::HiRes qw(usleep);++if ($ENV{enable_injection_points} ne 'yes')+{+ plan skip_all => 'Injection points not supported by this build';+}++my $node = PostgreSQL::Test::Cluster->new('primary');+$node->init(allows_streaming => 'logical');+$node->start;++if (!$node->check_extension('injection_points'))+{+ plan skip_all => 'Extension injection_points not installed';+}++$node->safe_psql('postgres', q(CREATE EXTENSION injection_points));+$node->safe_psql('postgres',+ q(CREATE TABLE tab(i int PRIMARY KEY, j int);+ INSERT INTO tab VALUES (1, 1), (2, 2)));++# Wait until the given backend waits on the lock of the given transaction.+sub wait_for_xact_lock_wait+{+ my ($pid, $xid, $what) = @_;++ $node->poll_query_until('postgres',+ "SELECT count(*) > 0 FROM pg_locks WHERE pid = $pid AND locktype = 'transactionid' AND transactionid = '$xid' AND NOT granted"+ ) or die "$what did not wait on the lock of transaction $xid";+}++my $s2 = $node->background_psql('postgres');+my $s3 = $node->background_psql('postgres');+my $s4 = $node->background_psql('postgres');+my $walsender = $node->background_psql('postgres', replication => 'database');++my $walsender_pid = $walsender->query_safe('SELECT pg_backend_pid()');+my $s4_pid = $s4->query_safe('SELECT pg_backend_pid()');++# s2 and s3 hold transactions with an XID, so that the snapshot builder goes+# through its BUILDING_SNAPSHOT and FULL_SNAPSHOT states rather than becoming+# consistent right away. The walsender waits for s2, then for s3.+my $s2_xid = $s2->query_safe('BEGIN; SELECT pg_current_xact_id()');++$walsender->query_until(+ qr/started/, q(\echo started+BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ;+CREATE_REPLICATION_SLOT slot_race TEMPORARY LOGICAL test_decoding USE_SNAPSHOT;+));+wait_for_xact_lock_wait($walsender_pid, $s2_xid, 'walsender');++my $s3_xid = $s3->query_safe('BEGIN; SELECT pg_current_xact_id()');+$s2->query_safe('ROLLBACK');+wait_for_xact_lock_wait($walsender_pid, $s3_xid, 'walsender');++# While the walsender waits for s3, s4 changes the table and stops after+# writing its commit record, before updating CLOG.+$s4->query_safe(+ q(SELECT injection_points_set_local();+ SELECT injection_points_attach('commit-before-clog-update', 'wait')));+$s4->query_until(+ qr/started/, q(\echo started+BEGIN;+INSERT INTO tab VALUES (3, 3);+UPDATE tab SET j = j + 1 WHERE i = 1;+DELETE FROM tab WHERE i = 2;+COMMIT;+));+$node->poll_query_until('postgres',+ "SELECT wait_event = 'commit-before-clog-update' FROM pg_stat_activity WHERE pid = $s4_pid"+) or die "s4 did not reach the injection point";+my $s4_xid = $node->safe_psql('postgres',+ "SELECT backend_xid FROM pg_stat_activity WHERE pid = $s4_pid");++# Now the walsender decodes s4's commit record and reaches a consistent+# state. It must wait for s4 rather than build the snapshot.+$s3->query_safe('ROLLBACK');+my $state;+for (my $i = 0; $i < 10 * $PostgreSQL::Test::Utils::timeout_default; $i++)+{+ $state = $node->safe_psql('postgres',+ "SELECT CASE WHEN a.state = 'idle in transaction' THEN 'slot created'+ WHEN l.pid IS NOT NULL THEN 'waiting for s4' END+ FROM pg_stat_activity a+ LEFT JOIN pg_locks l ON l.pid = a.pid AND l.locktype = 'transactionid'+ AND l.transactionid = '$s4_xid' AND NOT l.granted+ WHERE a.pid = $walsender_pid");+ last if $state ne '';+ usleep(100_000);+}+is($state, 'waiting for s4',+ 'slot creation waits for the transaction that has not updated CLOG');++# If the slot got created without waiting, use its snapshot right away:+# the scan takes s4 as aborted and sets hint bits accordingly, which is+# what the last two checks then report.+if ($state eq 'slot created')+{+ $walsender->query_until(qr/test_decoding/, '');+ diag("rows seen through the slot's snapshot before s4 updated CLOG: "+ . $walsender->query_safe('SELECT i, j FROM tab ORDER BY i'));+}++$node->safe_psql('postgres',+ "SELECT injection_points_wakeup('commit-before-clog-update')");+$s4->quit;++$node->poll_query_until('postgres',+ "SELECT state = 'idle in transaction' FROM pg_stat_activity WHERE pid = $walsender_pid"+) or die "slot creation did not finish";+# drain the result of CREATE_REPLICATION_SLOT+$walsender->query_until(qr/test_decoding/, '')+ if $state ne 'slot created';++is( $walsender->query_safe('SELECT i, j FROM tab ORDER BY i'),+ "1|2\n3|3",+ "the slot's snapshot sees the transaction's changes");+is( $node->safe_psql('postgres', 'SELECT i, j FROM tab ORDER BY i'),+ "1|2\n3|3",+ "a new session sees the transaction's changes");++$walsender->query_safe('ROLLBACK');+$walsender->quit;+$s2->quit;+$s3->quit;+$node->stop;++done_testing();--
2.47.3
--lxyes62whqnbwnus--
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: zhaorui126@gmail.com
Subject: Re: [PATCH v5 3/3] Test slot creation with USE_SNAPSHOT against a commit that is not in CLOG yet
In-Reply-To: <no-message-id-1871172@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox