diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml index 6347fe60b0c4..6363084bc94e 100644 --- a/doc/src/sgml/func/func-admin.sgml +++ b/doc/src/sgml/func/func-admin.sgml @@ -1012,6 +1012,11 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset are also relevant for replication. + + Note that slot manipulation functions except pg_drop_replication_slot + cannot be used in single-user mode. + + Replication Management Functions diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index ca53caac2f2f..30877fdc3aba 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -113,6 +113,9 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin List *options = NIL; DecodingOutputState *p; + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckLogicalDecodingRequirements(); diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 8605776ad863..4ca1227cc943 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -653,7 +653,7 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid) } else { - active_pid = MyProcPid; + s->active_pid = active_pid = MyProcPid; ReplicationSlotSetInactiveSince(s, 0, true); } LWLockRelease(ReplicationSlotControlLock); @@ -1529,6 +1529,18 @@ CheckSlotPermissions(void) "REPLICATION"))); } +/* + * Check whether the instance is in single-user mode. + */ +void +CheckSlotIsInSingleUserMode(void) +{ + if (!IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots cannot be used in single-user mode"))); +} + /* * Reserve WAL for the currently active slot. * diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 69f4c6157c51..90fe63283a15 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -17,6 +17,7 @@ #include "access/xlogrecovery.h" #include "access/xlogutils.h" #include "funcapi.h" +#include "miscadmin.h" #include "replication/logical.h" #include "replication/slot.h" #include "replication/slotsync.h" @@ -76,6 +77,9 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckSlotRequirements(); @@ -182,6 +186,9 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS) if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); CheckLogicalDecodingRequirements(); @@ -521,6 +528,9 @@ pg_replication_slot_advance(PG_FUNCTION_ARGS) Assert(!MyReplicationSlot); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); if (XLogRecPtrIsInvalid(moveto)) @@ -618,9 +628,13 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) TupleDesc tupdesc; HeapTuple tuple; + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) elog(ERROR, "return type must be a row type"); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + CheckSlotPermissions(); if (logical_slot) diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c index a4f8b4faa90d..337f705b34b6 100644 --- a/src/backend/utils/adt/pg_upgrade_support.c +++ b/src/backend/utils/adt/pg_upgrade_support.c @@ -296,6 +296,9 @@ binary_upgrade_logical_slot_has_caught_up(PG_FUNCTION_ARGS) */ Assert(has_rolreplication(GetUserId())); + /* Slot manipulation is not allowed in single-user mode */ + CheckSlotIsInSingleUserMode(); + slot_name = PG_GETARG_NAME(0); /* Acquire the given slot */ diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index e8fc342d1a96..b4d89760f964 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -342,6 +342,7 @@ extern void CheckPointReplicationSlots(bool is_shutdown); extern void CheckSlotRequirements(void); extern void CheckSlotPermissions(void); +extern void CheckSlotIsInSingleUserMode(void); extern ReplicationSlotInvalidationCause GetSlotInvalidationCause(const char *cause_name); extern const char *GetSlotInvalidationCauseName(ReplicationSlotInvalidationCause cause); diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build index 9c50de7efb0f..08357d2f2e8a 100644 --- a/src/test/modules/test_misc/meson.build +++ b/src/test/modules/test_misc/meson.build @@ -16,6 +16,7 @@ tests += { 't/005_timeouts.pl', 't/006_signal_autovacuum.pl', 't/007_catcache_inval.pl', + 't/008_slots_in_single_user_mode.pl', ], }, } diff --git a/src/test/modules/test_misc/t/008_slots_in_single_user_mode.pl b/src/test/modules/test_misc/t/008_slots_in_single_user_mode.pl new file mode 100644 index 000000000000..b3d9fe2fc332 --- /dev/null +++ b/src/test/modules/test_misc/t/008_slots_in_single_user_mode.pl @@ -0,0 +1,99 @@ +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Tests the slot manipulation in the single-user mode + +# Skip the whole thing on the windows platform +if ($windows_os) +{ + plan skip_all => 'this test is not supported by this platform'; +} + +# Run passed commands in single-user mode. The return value from the command is +# passed through. +sub run_test_in_single_user_mode +{ + my ($node, $commands, $testname) = @_; + + my $result = run_log( + [ + 'postgres', '--single', '-F', + '-c' => 'exit_on_error=true', + '-D' => $node->data_dir, + 'postgres' + ], + \$commands); + + return $result; +} + +# Wrapper function for run_test_in_single_user_mode. This would be used when +# the input command succeeds. +sub run_test_in_single_user_mode_success +{ + my ($node, $commands, $testname) = @_; + + my $result = run_test_in_single_user_mode($node, $commands, $testname); + + ok($result, $testname); +} + +# Wrapper function for run_test_in_single_user_mode. This would be used when +# the input command fails. +sub run_test_in_single_user_mode_fail +{ + my ($node, $commands, $testname) = @_; + + my $result = run_test_in_single_user_mode($node, $commands, $testname); + + ok(!$result, $testname); +} + +my $slotname = 'test_slot'; + +# Initialize a node +my $node = PostgreSQL::Test::Cluster->new('node'); +$node->init(allows_streaming => "logical"); +$node->start; + +# Define initial table +$node->safe_psql( + 'postgres', qq( +CREATE TABLE foo (id int); +SELECT pg_create_logical_replication_slot('$slotname', 'test_decoding'); +)); + +# Stop the node to run and test in single-user mode +$node->stop; + +run_test_in_single_user_mode_fail( + $node, + "SELECT pg_create_logical_replication_slot('another_slot', 'test_decoding')", + "replication slot cannot be created in single-user mode"); + +run_test_in_single_user_mode_fail( + $node, qq( +INSERT INTO foo VALUES (1); +SELECT count(1) FROM pg_logical_slot_get_changes('$slotname', NULL, NULL); +), + "logical decoding cannot be done in single-user mode"); + +run_test_in_single_user_mode_fail( + $node, + "SELECT pg_replication_slot_advance('$slotname', pg_current_wal_lsn())", + "replication slot cannot be advanced in single-user mode"); + +run_test_in_single_user_mode_fail( + $node, + "SELECT pg_copy_logical_replication_slot('$slotname', 'dest_slot')", + "replication slot cannot be copied in single-user mode"); + +run_test_in_single_user_mode_success( + $node, + "SELECT pg_drop_replication_slot('$slotname')", + "replication slot can be dropped in single-user mode"); + +done_testing(); diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index 52993c32dbba..0b79896b61cf 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -56,7 +56,7 @@ tests += { 't/045_archive_restartpoint.pl', 't/046_checkpoint_logical_slot.pl', 't/047_checkpoint_physical_slot.pl', - 't/048_vacuum_horizon_floor.pl' + 't/048_vacuum_horizon_floor.pl', ], }, }