From 629a0eab98e5f040f7b4b3f6d16a4b0ece8d0063 Mon Sep 17 00:00:00 2001 From: Shlok Kyal Date: Tue, 4 Feb 2025 14:58:32 +0530 Subject: [PATCH v3] Restrict copying of invalidated replication slots Currently we can copy an invalidated logical and physical replication slot using functions 'pg_copy_logical_replication_slot' and 'pg_copy_physical_replication_slot' respectively. With this patch we will throw an error in such cases. --- doc/src/sgml/func.sgml | 4 +- src/backend/replication/slotfuncs.c | 37 +++++++++++++++++++ .../t/035_standby_logical_decoding.pl | 9 +++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index df32ee0bf5b..1bed0103723 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -29347,7 +29347,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The copied physical slot starts to reserve WAL from the same LSN as the source slot. temporary is optional. If temporary - is omitted, the same value as the source slot is used. + is omitted, the same value as the source slot is used. Copy of an + invalidated physical replication slot in not allowed. @@ -29369,6 +29370,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset from the same LSN as the source logical slot. Both temporary and plugin are optional; if they are omitted, the values of the source slot are used. + Copy of an invalidated logical replication slot in not allowed. diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index f652ec8a73e..c845e16f80a 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -604,6 +604,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) ReplicationSlot second_slot_contents; XLogRecPtr src_restart_lsn; bool src_islogical; + bool src_isinvalidated; bool temporary; char *plugin; Datum values[2]; @@ -661,6 +662,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) src_restart_lsn = first_slot_contents.data.restart_lsn; temporary = (first_slot_contents.data.persistency == RS_TEMPORARY); plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL; + src_isinvalidated = (first_slot_contents.data.invalidated != RS_INVAL_NONE); /* Check type of replication slot */ if (src_islogical != logical_slot) @@ -678,6 +680,13 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("cannot copy a replication slot that doesn't reserve WAL"))); + /* Cannot copy an invalidated replication slot */ + if (src_isinvalidated) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot copy invalidated replication slot \"%s\"", + NameStr(*src_name))); + /* Overwrite params from optional arguments */ if (PG_NARGS() >= 3) temporary = PG_GETARG_BOOL(2); @@ -774,6 +783,34 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot) NameStr(*src_name)), errhint("Retry when the source replication slot's confirmed_flush_lsn is valid."))); + /* Check if source slot was invalidated while copying of slot */ + LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); + + for (int i = 0; i < max_replication_slots; i++) + { + ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i]; + + if (s->in_use && strcmp(NameStr(s->data.name), NameStr(*src_name)) == 0) + { + /* Copy the slot contents while holding spinlock */ + SpinLockAcquire(&s->mutex); + first_slot_contents = *s; + SpinLockRelease(&s->mutex); + src = s; + break; + } + } + + LWLockRelease(ReplicationSlotControlLock); + + src_isinvalidated = (first_slot_contents.data.invalidated != RS_INVAL_NONE); + + if (src_isinvalidated) + ereport(ERROR, + (errmsg("could not copy replication slot \"%s\"", + NameStr(*src_name)), + errdetail("The source replication slot was invalidated during the copy operation."))); + /* Install copied values again */ SpinLockAcquire(&MyReplicationSlot->mutex); MyReplicationSlot->effective_xmin = copy_effective_xmin; diff --git a/src/test/recovery/t/035_standby_logical_decoding.pl b/src/test/recovery/t/035_standby_logical_decoding.pl index 505e85d1eb6..23b235412ac 100644 --- a/src/test/recovery/t/035_standby_logical_decoding.pl +++ b/src/test/recovery/t/035_standby_logical_decoding.pl @@ -553,6 +553,15 @@ $handle = check_pg_recvlogical_stderr($handle, "can no longer access replication slot \"vacuum_full_activeslot\""); +# Attempt to copy an invalidated logical replication slot +($result, $stdout, $stderr) = $node_standby->psql( + 'postgres', + qq[select pg_copy_logical_replication_slot('vacuum_full_inactiveslot', 'vacuum_full_inactiveslot_copy');], + replication => 'database'); +ok( $stderr =~ + /ERROR: cannot copy invalidated replication slot "vacuum_full_inactiveslot"/, + "invalidated slot cannot be copied"); + # Turn hot_standby_feedback back on change_hot_standby_feedback_and_wait_for_xmins(1, 1); -- 2.34.1