Skip to content

Commit fd09c13

Browse files
Restrict copying of invalidated replication slots.
Previously, invalidated logical and physical replication slots could be copied using the pg_copy_logical_replication_slot and pg_copy_physical_replication_slot functions. Replication slots that were invalidated for reasons other than WAL removal retained their restart_lsn. This meant that a new slot copied from an invalidated slot could have a restart_lsn pointing to a WAL segment that might have already been removed. This commit restricts the copying of invalidated replication slots. Backpatch to v16, where slots could retain their restart_lsn when invalidated for reasons other than WAL removal. For v15 and earlier, this check is not required since slots can only be invalidated due to WAL removal, and existing checks already handle this issue. Author: Shlok Kyal <shlok.kyal.oss@gmail.com> Reviewed-by: vignesh C <vignesh21@gmail.com> Reviewed-by: Zhijie Hou <houzj.fnst@fujitsu.com> Reviewed-by: Peter Smith <smithpb2250@gmail.com> Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/CANhcyEU65aH0VYnLiu%3DOhNNxhnhNhwcXBeT-jvRe1OiJTo_Ayg%40mail.gmail.com Backpatch-through: 16
1 parent f104192 commit fd09c13

File tree

3 files changed

+34
-2
lines changed

3 files changed

+34
-2
lines changed

doc/src/sgml/func.sgml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29598,7 +29598,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
2959829598
The copied physical slot starts to reserve WAL from the same <acronym>LSN</acronym> as the
2959929599
source slot.
2960029600
<parameter>temporary</parameter> is optional. If <parameter>temporary</parameter>
29601-
is omitted, the same value as the source slot is used.
29601+
is omitted, the same value as the source slot is used. Copy of an
29602+
invalidated slot is not allowed.
2960229603
</para></entry>
2960329604
</row>
2960429605

@@ -29623,7 +29624,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
2962329624
The <literal>failover</literal> option of the source logical slot
2962429625
is not copied and is set to <literal>false</literal> by default. This
2962529626
is to avoid the risk of being unable to continue logical replication
29626-
after failover to standby where the slot is being synchronized.
29627+
after failover to standby where the slot is being synchronized. Copy of
29628+
an invalidated slot is not allowed.
2962729629
</para></entry>
2962829630
</row>
2962929631

src/backend/replication/slotfuncs.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,13 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
684684
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
685685
errmsg("cannot copy a replication slot that doesn't reserve WAL")));
686686

687+
/* Cannot copy an invalidated replication slot */
688+
if (first_slot_contents.data.invalidated != RS_INVAL_NONE)
689+
ereport(ERROR,
690+
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
691+
errmsg("cannot copy invalidated replication slot \"%s\"",
692+
NameStr(*src_name)));
693+
687694
/* Overwrite params from optional arguments */
688695
if (PG_NARGS() >= 3)
689696
temporary = PG_GETARG_BOOL(2);
@@ -785,6 +792,20 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
785792
NameStr(*src_name)),
786793
errhint("Retry when the source replication slot's confirmed_flush_lsn is valid.")));
787794

795+
/*
796+
* Copying an invalid slot doesn't make sense. Note that the source
797+
* slot can become invalid after we create the new slot and copy the
798+
* data of source slot. This is possible because the operations in
799+
* InvalidateObsoleteReplicationSlots() are not serialized with this
800+
* function. Even though we can't detect such a case here, the copied
801+
* slot will become invalid in the next checkpoint cycle.
802+
*/
803+
if (second_slot_contents.data.invalidated != RS_INVAL_NONE)
804+
ereport(ERROR,
805+
errmsg("cannot copy replication slot \"%s\"",
806+
NameStr(*src_name)),
807+
errdetail("The source replication slot was invalidated during the copy operation."));
808+
788809
/* Install copied values again */
789810
SpinLockAcquire(&MyReplicationSlot->mutex);
790811
MyReplicationSlot->effective_xmin = copy_effective_xmin;

src/test/recovery/t/035_standby_logical_decoding.pl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,15 @@ sub wait_until_vacuum_can_remove
591591
check_pg_recvlogical_stderr($handle,
592592
"can no longer access replication slot \"vacuum_full_activeslot\"");
593593

594+
# Attempt to copy an invalidated logical replication slot
595+
($result, $stdout, $stderr) = $node_standby->psql(
596+
'postgres',
597+
qq[select pg_copy_logical_replication_slot('vacuum_full_inactiveslot', 'vacuum_full_inactiveslot_copy');],
598+
replication => 'database');
599+
ok( $stderr =~
600+
/ERROR: cannot copy invalidated replication slot "vacuum_full_inactiveslot"/,
601+
"invalidated slot cannot be copied");
602+
594603
# Turn hot_standby_feedback back on
595604
change_hot_standby_feedback_and_wait_for_xmins(1, 1);
596605

0 commit comments

Comments
 (0)