Fix assertion failure with replication slot release in single-user mode
authorMichael Paquier <[email protected]>
Wed, 20 Aug 2025 06:00:04 +0000 (15:00 +0900)
committerMichael Paquier <[email protected]>
Wed, 20 Aug 2025 06:00:04 +0000 (15:00 +0900)
Some replication slot manipulations (logical decoding via SQL,
advancing) were failing an assertion when releasing a slot in
single-user mode, because active_pid was not set in a ReplicationSlot
when its slot is acquired.

ReplicationSlotAcquire() has some logic to be able to work with the
single-user mode.  This commit sets ReplicationSlot->active_pid to
MyProcPid, to let the slot-related logic fall-through, considering the
single process as the one holding the slot.

Some TAP tests are added for various replication slot functions with the
single-user mode, while on it, for slot creation, drop, advancing, copy
and logical decoding with multiple slot types (temporary, physical vs
logical).  These tests are skipped on Windows, as direct calls of
postgres --single would fail on permission failures.  There is no
platform-specific behavior that needs to be checked, so living with this
restriction should be fine.  The CI is OK with that, now let's see what
the buildfarm tells.

Author: Hayato Kuroda <[email protected]>
Reviewed-by: Paul A. Jungwirth <[email protected]>
Reviewed-by: Mutaamba Maasha <[email protected]>
Discussion: https://postgr.es/m/OSCPR01MB14966ED588A0328DAEBE8CB25F5FA2@OSCPR01MB14966.jpnprd01.prod.outlook.com
Backpatch-through: 13

src/backend/replication/slot.c
src/test/modules/test_misc/Makefile
src/test/modules/test_misc/meson.build
src/test/modules/test_misc/t/008_replslot_single_user.pl [new file with mode: 0644]

index 8605776ad8631706ab7ac6c8ec63decbf3ffcfd0..fd0fdb96d4246797e3e4727ba981eaf766ade49b 100644 (file)
@@ -653,7 +653,7 @@ retry:
    }
    else
    {
-       active_pid = MyProcPid;
+       s->active_pid = active_pid = MyProcPid;
        ReplicationSlotSetInactiveSince(s, 0, true);
    }
    LWLockRelease(ReplicationSlotControlLock);
index 919a25fc67fd335ed2974037366168d918c33de7..399b9094a3880fddefe1a17db85420bca8be4846 100644 (file)
@@ -2,7 +2,8 @@
 
 TAP_TESTS = 1
 
-EXTRA_INSTALL=src/test/modules/injection_points
+EXTRA_INSTALL=src/test/modules/injection_points \
+   contrib/test_decoding
 
 export enable_injection_points
 
index 9c50de7efb0f701068d41a0701e4d00ca21ead0d..6b1e730bf46d03ec0716d1ddd8ae627978ee976b 100644 (file)
@@ -16,6 +16,7 @@ tests += {
       't/005_timeouts.pl',
       't/006_signal_autovacuum.pl',
       't/007_catcache_inval.pl',
+      't/008_replslot_single_user.pl',
     ],
   },
 }
diff --git a/src/test/modules/test_misc/t/008_replslot_single_user.pl b/src/test/modules/test_misc/t/008_replslot_single_user.pl
new file mode 100644 (file)
index 0000000..796700d
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+# Test manipulations of replication slots with the single-user mode.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Skip the tests on Windows, as single-user mode would fail on permission
+# failure with privileged accounts.
+if ($windows_os)
+{
+   plan skip_all => 'this test is not supported by this platform';
+}
+
+# Run set of queries in single-user mode.
+sub test_single_mode
+{
+   my ($node, $queries, $testname) = @_;
+
+   my $result = run_log(
+       [
+           'postgres', '--single', '-F',
+           '-c' => 'exit_on_error=true',
+           '-D' => $node->data_dir,
+           'postgres'
+       ],
+       '<' => \$queries);
+
+   ok($result, $testname);
+}
+
+my $slot_logical = 'slot_logical';
+my $slot_physical = 'slot_physical';
+
+# Initialize a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(allows_streaming => "logical");
+$node->start;
+
+# Define initial table
+$node->safe_psql('postgres', "CREATE TABLE foo (id int)");
+
+$node->stop;
+
+test_single_mode(
+   $node,
+   "SELECT pg_create_logical_replication_slot('$slot_logical', 'test_decoding')",
+   "logical slot creation");
+test_single_mode(
+   $node,
+   "SELECT pg_create_physical_replication_slot('$slot_physical', true)",
+   "physical slot creation");
+test_single_mode(
+   $node,
+   "SELECT pg_create_physical_replication_slot('slot_tmp', true, true)",
+   "temporary physical slot creation");
+
+test_single_mode(
+   $node, qq(
+INSERT INTO foo VALUES (1);
+SELECT pg_logical_slot_get_changes('$slot_logical', NULL, NULL);
+),
+   "logical decoding");
+
+test_single_mode(
+   $node,
+   "SELECT pg_replication_slot_advance('$slot_logical', pg_current_wal_lsn())",
+   "logical slot advance");
+test_single_mode(
+   $node,
+   "SELECT pg_replication_slot_advance('$slot_physical', pg_current_wal_lsn())",
+   "physical slot advance");
+
+test_single_mode(
+   $node,
+   "SELECT pg_copy_logical_replication_slot('$slot_logical', 'slot_log_copy')",
+   "logical slot copy");
+test_single_mode(
+   $node,
+   "SELECT pg_copy_physical_replication_slot('$slot_physical', 'slot_phy_copy')",
+   "physical slot copy");
+
+test_single_mode(
+   $node,
+   "SELECT pg_drop_replication_slot('$slot_logical')",
+   "logical slot drop");
+test_single_mode(
+   $node,
+   "SELECT pg_drop_replication_slot('$slot_physical')",
+   "physical slot drop");
+
+done_testing();