Fix assertion failure with replication slot release in single-user mode
authorMichael Paquier <michael@paquier.xyz>
Wed, 20 Aug 2025 06:00:13 +0000 (15:00 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 20 Aug 2025 06:00:13 +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 <kuroda.hayato@fujitsu.com>
Reviewed-by: Paul A. Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Mutaamba Maasha <maasha@gmail.com>
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 a223d80f715181e30db9a5ba4a421a8da681a0ed..4508408c79bcd5c086cab147be8c23aa03d3dec4 100644 (file)
@@ -496,7 +496,7 @@ retry:
        SpinLockRelease(&s->mutex);
    }
    else
-       active_pid = MyProcPid;
+       s->active_pid = active_pid = MyProcPid;
    LWLockRelease(ReplicationSlotControlLock);
 
    /*
index 39c6c2014a02a97b318debe5b1374322c5a560da..06a8ee9d03abf2bf0018354208cc8fdc8c3fc552 100644 (file)
@@ -2,6 +2,8 @@
 
 TAP_TESTS = 1
 
+EXTRA_INSTALL = contrib/test_decoding
+
 ifdef USE_PGXS
 PG_CONFIG = pg_config
 PGXS := $(shell $(PG_CONFIG) --pgxs)
index 911084ac0fe93138cb18b5a257e79815b0b9bb1c..61dbd69cea70c890a5565815779fcc80a101b299 100644 (file)
@@ -10,6 +10,7 @@ tests += {
       't/002_tablespace.pl',
       't/003_check_guc.pl',
       't/004_io_direct.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();