From e430f5563f3cf9ff56ffd4c773a5cf785d39bc18 Mon Sep 17 00:00:00 2001
From: Shveta Malik <shveta.malik@gmail.com>
Date: Fri, 16 Feb 2024 12:44:58 +0530
Subject: [PATCH v89 1/3] Add a new slotsync worker

By enabling slot synchronization, all the failover logical replication slots on
the primary (assuming configurations are appropriate) are automatically created
on the physical standbys and are synced periodically. Slot-sync worker on the
standby server ping the primary server at regular intervals to get the
necessary failover logical slots information and create/update the slots
locally. The slots that no longer require synchronization are automatically
dropped by the worker.

The nap time of the worker is tuned according to the activity on the primary.
The worker waits for a period of time before the next synchronization, with the
duration varying based on whether any slots were updated during the last
cycle.

A new parameter sync_replication_slots enables or disables this new process.

On promotion, the slot sync worker is shut down by the startup process to
drop any temporary slots acquired by the slot sync worker and to prevent
the worker from keep trying to fetch the failover slots.
---
 doc/src/sgml/config.sgml                      |  18 +
 doc/src/sgml/logicaldecoding.sgml             |   5 +-
 src/backend/access/transam/xlogrecovery.c     |  15 +
 src/backend/postmaster/postmaster.c           |  88 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   3 +
 src/backend/replication/logical/slotsync.c    | 809 ++++++++++++++++--
 src/backend/replication/slot.c                |  14 +
 src/backend/replication/slotfuncs.c           |   2 +-
 src/backend/replication/walsender.c           |   2 +-
 src/backend/storage/ipc/ipci.c                |   4 +-
 src/backend/storage/lmgr/proc.c               |  13 +-
 src/backend/utils/activity/pgstat_io.c        |   1 +
 .../utils/activity/wait_event_names.txt       |   2 +
 src/backend/utils/init/miscinit.c             |   9 +-
 src/backend/utils/init/postinit.c             |   8 +-
 src/backend/utils/misc/guc_tables.c           |  10 +
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/miscadmin.h                       |   1 +
 src/include/replication/slotsync.h            |  24 +-
 .../t/040_standby_failover_slots_sync.pl      | 113 ++-
 src/tools/pgindent/typedefs.list              |   2 +-
 21 files changed, 1044 insertions(+), 100 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ffd711b7f2..ff184003fe 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4943,6 +4943,24 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-sync-replication-slots" xreflabel="sync_replication_slots">
+      <term><varname>sync_replication_slots</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>sync_replication_slots</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        It enables a physical standby to synchronize logical failover slots
+        from the primary server so that logical subscribers can resume
+        replication from the new primary server after failover.
+       </para>
+       <para>
+        It is disabled by default. This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
     </sect2>
 
diff --git a/doc/src/sgml/logicaldecoding.sgml b/doc/src/sgml/logicaldecoding.sgml
index eceaaaa273..930c0fa8a6 100644
--- a/doc/src/sgml/logicaldecoding.sgml
+++ b/doc/src/sgml/logicaldecoding.sgml
@@ -373,7 +373,10 @@ postgres=# select * from pg_logical_slot_get_changes('regression_slot', NULL, NU
      <command>CREATE SUBSCRIPTION</command> during slot creation, and then calling
      <link linkend="pg-sync-replication-slots">
      <function>pg_sync_replication_slots</function></link>
-     on the standby. For the synchronization to work, it is mandatory to
+     on the standby. By setting <link linkend="guc-sync-replication-slots">
+     <varname>sync_replication_slots</varname></link>
+     on the standby, the failover slots can be synchronized periodically in
+     the slotsync worker. For the synchronization to work, it is mandatory to
      have a physical replication slot between the primary and the standby aka
      <link linkend="guc-primary-slot-name"><varname>primary_slot_name</varname></link>
      should be configured on the standby, and
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 0bb472da27..d73a49b3e8 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -49,6 +49,7 @@
 #include "postmaster/bgwriter.h"
 #include "postmaster/startup.h"
 #include "replication/slot.h"
+#include "replication/slotsync.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -1467,6 +1468,20 @@ FinishWalRecovery(void)
 	 */
 	XLogShutdownWalRcv();
 
+	/*
+	 * Shutdown the slot sync worker to drop any temporary slots acquired by
+	 * it and to prevent it from keep trying to fetch the failover slots.
+	 *
+	 * We do not update the 'synced' column from true to false here, as any
+	 * failed update could leave 'synced' column false for some slots. This
+	 * could cause issues during slot sync after restarting the server as a
+	 * standby. While updating the 'synced' column after switching to the new
+	 * timeline is an option, it does not simplify the handling for the
+	 * 'synced' column. Therefore, we retain the 'synced' column as true after
+	 * promotion as it may provide useful information about the slot origin.
+	 */
+	ShutDownSlotSync();
+
 	/*
 	 * We are now done reading the xlog from stream. Turn off streaming
 	 * recovery to force fetching the files (which would be required at end of
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index df945a5ac4..3080d4aa53 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -115,6 +115,7 @@
 #include "postmaster/syslogger.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/logicallauncher.h"
+#include "replication/slotsync.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
@@ -167,11 +168,11 @@
  * they will never become live backends.  dead_end children are not assigned a
  * PMChildSlot.  dead_end children have bkend_type NORMAL.
  *
- * "Special" children such as the startup, bgwriter and autovacuum launcher
- * tasks are not in this list.  They are tracked via StartupPID and other
- * pid_t variables below.  (Thus, there can't be more than one of any given
- * "special" child process type.  We use BackendList entries for any child
- * process there can be more than one of.)
+ * "Special" children such as the startup, bgwriter, autovacuum launcher, and
+ * slot sync worker tasks are not in this list.  They are tracked via StartupPID
+ * and other pid_t variables below.  (Thus, there can't be more than one of any
+ * given "special" child process type.  We use BackendList entries for any
+ * child process there can be more than one of.)
  */
 typedef struct bkend
 {
@@ -254,7 +255,8 @@ static pid_t StartupPID = 0,
 			WalSummarizerPID = 0,
 			AutoVacPID = 0,
 			PgArchPID = 0,
-			SysLoggerPID = 0;
+			SysLoggerPID = 0,
+			SlotSyncWorkerPID = 0;
 
 /* Startup process's status */
 typedef enum
@@ -458,6 +460,17 @@ static void InitPostmasterDeathWatchHandle(void);
 	   (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY))) && \
 	 PgArchCanRestart())
 
+/*
+ * Slot sync worker allowed to start up?
+ *
+ * If we are on a hot standby, slot sync parameter is enabled, and it is
+ * the first time of worker's launch, or enough time has passed since the
+ * worker was launched last, then it is allowed to be started.
+ */
+#define SlotSyncWorkerAllowed()	\
+	(sync_replication_slots && pmState == PM_HOT_STANDBY && \
+	 SlotSyncWorkerCanRestart())
+
 #ifdef EXEC_BACKEND
 
 #ifdef WIN32
@@ -1822,6 +1835,10 @@ ServerLoop(void)
 		if (PgArchPID == 0 && PgArchStartupAllowed())
 			PgArchPID = StartChildProcess(ArchiverProcess);
 
+		/* If we need to start a slot sync worker, try to do that now */
+		if (SlotSyncWorkerPID == 0 && SlotSyncWorkerAllowed())
+			SlotSyncWorkerPID = StartSlotSyncWorker();
+
 		/* If we need to signal the autovacuum launcher, do so now */
 		if (avlauncher_needs_signal)
 		{
@@ -2661,6 +2678,8 @@ process_pm_reload_request(void)
 			signal_child(PgArchPID, SIGHUP);
 		if (SysLoggerPID != 0)
 			signal_child(SysLoggerPID, SIGHUP);
+		if (SlotSyncWorkerPID != 0)
+			signal_child(SlotSyncWorkerPID, SIGHUP);
 
 		/* Reload authentication config files too */
 		if (!load_hba())
@@ -3011,6 +3030,9 @@ process_pm_child_exit(void)
 			if (PgArchStartupAllowed() && PgArchPID == 0)
 				PgArchPID = StartChildProcess(ArchiverProcess);
 
+			if (SlotSyncWorkerAllowed() && SlotSyncWorkerPID == 0)
+				SlotSyncWorkerPID = StartSlotSyncWorker();
+
 			/* workers may be scheduled to start now */
 			maybe_start_bgworkers();
 
@@ -3180,6 +3202,22 @@ process_pm_child_exit(void)
 			continue;
 		}
 
+		/*
+		 * Was it the slot sync worker? Normal exit or FATAL exit can be
+		 * ignored (FATAL can be caused by libpqwalreceiver on receiving
+		 * shutdown request by the startup process during promotion); we'll
+		 * start a new one at the next iteration of the postmaster's main
+		 * loop, if necessary. Any other exit condition is treated as a crash.
+		 */
+		if (pid == SlotSyncWorkerPID)
+		{
+			SlotSyncWorkerPID = 0;
+			if (!EXIT_STATUS_0(exitstatus) && !EXIT_STATUS_1(exitstatus))
+				HandleChildCrash(pid, exitstatus,
+								 _("slot sync worker process"));
+			continue;
+		}
+
 		/* Was it one of our background workers? */
 		if (CleanupBackgroundWorker(pid, exitstatus))
 		{
@@ -3384,7 +3422,7 @@ CleanupBackend(int pid,
 
 /*
  * HandleChildCrash -- cleanup after failed backend, bgwriter, checkpointer,
- * walwriter, autovacuum, archiver or background worker.
+ * walwriter, autovacuum, archiver, slot sync worker, or background worker.
  *
  * The objectives here are to clean up our local state about the child
  * process, and to signal all other remaining children to quickdie.
@@ -3546,6 +3584,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname)
 	else if (PgArchPID != 0 && take_action)
 		sigquit_child(PgArchPID);
 
+	/* Take care of the slot sync worker too */
+	if (pid == SlotSyncWorkerPID)
+		SlotSyncWorkerPID = 0;
+	else if (SlotSyncWorkerPID != 0 && take_action)
+		sigquit_child(SlotSyncWorkerPID);
+
 	/* We do NOT restart the syslogger */
 
 	if (Shutdown != ImmediateShutdown)
@@ -3686,6 +3730,8 @@ PostmasterStateMachine(void)
 			signal_child(WalReceiverPID, SIGTERM);
 		if (WalSummarizerPID != 0)
 			signal_child(WalSummarizerPID, SIGTERM);
+		if (SlotSyncWorkerPID != 0)
+			signal_child(SlotSyncWorkerPID, SIGTERM);
 		/* checkpointer, archiver, stats, and syslogger may continue for now */
 
 		/* Now transition to PM_WAIT_BACKENDS state to wait for them to die */
@@ -3701,13 +3747,13 @@ PostmasterStateMachine(void)
 		/*
 		 * PM_WAIT_BACKENDS state ends when we have no regular backends
 		 * (including autovac workers), no bgworkers (including unconnected
-		 * ones), and no walwriter, autovac launcher or bgwriter.  If we are
-		 * doing crash recovery or an immediate shutdown then we expect the
-		 * checkpointer to exit as well, otherwise not. The stats and
-		 * syslogger processes are disregarded since they are not connected to
-		 * shared memory; we also disregard dead_end children here. Walsenders
-		 * and archiver are also disregarded, they will be terminated later
-		 * after writing the checkpoint record.
+		 * ones), and no walwriter, autovac launcher, bgwriter or slot sync
+		 * worker.  If we are doing crash recovery or an immediate shutdown
+		 * then we expect the checkpointer to exit as well, otherwise not. The
+		 * stats and syslogger processes are disregarded since they are not
+		 * connected to shared memory; we also disregard dead_end children
+		 * here. Walsenders and archiver are also disregarded, they will be
+		 * terminated later after writing the checkpoint record.
 		 */
 		if (CountChildren(BACKEND_TYPE_ALL - BACKEND_TYPE_WALSND) == 0 &&
 			StartupPID == 0 &&
@@ -3717,7 +3763,8 @@ PostmasterStateMachine(void)
 			(CheckpointerPID == 0 ||
 			 (!FatalError && Shutdown < ImmediateShutdown)) &&
 			WalWriterPID == 0 &&
-			AutoVacPID == 0)
+			AutoVacPID == 0 &&
+			SlotSyncWorkerPID == 0)
 		{
 			if (Shutdown >= ImmediateShutdown || FatalError)
 			{
@@ -3815,6 +3862,7 @@ PostmasterStateMachine(void)
 			Assert(CheckpointerPID == 0);
 			Assert(WalWriterPID == 0);
 			Assert(AutoVacPID == 0);
+			Assert(SlotSyncWorkerPID == 0);
 			/* syslogger is not considered here */
 			pmState = PM_NO_CHILDREN;
 		}
@@ -4038,6 +4086,8 @@ TerminateChildren(int signal)
 		signal_child(AutoVacPID, signal);
 	if (PgArchPID != 0)
 		signal_child(PgArchPID, signal);
+	if (SlotSyncWorkerPID != 0)
+		signal_child(SlotSyncWorkerPID, signal);
 }
 
 /*
@@ -4850,6 +4900,7 @@ SubPostmasterMain(int argc, char *argv[])
 	 */
 	if (strcmp(argv[1], "--forkbackend") == 0 ||
 		strcmp(argv[1], "--forkavlauncher") == 0 ||
+		strcmp(argv[1], "--forkssworker") == 0 ||
 		strcmp(argv[1], "--forkavworker") == 0 ||
 		strcmp(argv[1], "--forkaux") == 0 ||
 		strcmp(argv[1], "--forkbgworker") == 0)
@@ -4953,6 +5004,13 @@ SubPostmasterMain(int argc, char *argv[])
 
 		AutoVacWorkerMain(argc - 2, argv + 2);	/* does not return */
 	}
+	if (strcmp(argv[1], "--forkssworker") == 0)
+	{
+		/* Restore basic shared memory pointers */
+		InitShmemAccess(UsedShmemSegAddr);
+
+		ReplSlotSyncWorkerMain(argc - 2, argv + 2); /* does not return */
+	}
 	if (strcmp(argv[1], "--forkbgworker") == 0)
 	{
 		/* do this as early as possible; in particular, before InitProcess() */
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 9270d7b855..04271ee703 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -6,6 +6,9 @@
  * loaded as a dynamic module to avoid linking the main server binary with
  * libpq.
  *
+ * Apart from walreceiver, the libpq-specific routines are now being used by
+ * logical replication workers and slot synchronization.
+ *
  * Portions Copyright (c) 2010-2024, PostgreSQL Global Development Group
  *
  *
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 4cab7b7101..1f6ba0762e 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -10,18 +10,25 @@
  *
  * This file contains the code for slot synchronization on a physical standby
  * to fetch logical failover slots information from the primary server, create
- * the slots on the standby and synchronize them. This is done by a call to SQL
- * function pg_sync_replication_slots.
+ * the slots on the standby and synchronize them periodically.
  *
- * If on physical standby, the WAL corresponding to the remote's restart_lsn
- * is not available or the remote's catalog_xmin precedes the oldest xid for which
- * it is guaranteed that rows wouldn't have been removed then we cannot create
- * the local standby slot because that would mean moving the local slot
+ * Slot synchronization can be performed either automatically by enabling slot
+ * sync worker or manually by calling SQL function pg_sync_replication_slots().
+ *
+ * If the WAL corresponding to the remote's restart_lsn is not available on the
+ * physical standby or the remote's catalog_xmin precedes the oldest xid for
+ * which it is guaranteed that rows wouldn't have been removed then we cannot
+ * create the local standby slot because that would mean moving the local slot
  * backward and decoding won't be possible via such a slot. In this case, the
  * slot will be marked as RS_TEMPORARY. Once the primary server catches up,
  * the slot will be marked as RS_PERSISTENT (which means sync-ready) after
- * which we can call pg_sync_replication_slots() periodically to perform
- * syncs.
+ * which slot sync worker can perform the sync periodically or user can call
+ * pg_sync_replication_slots() periodically to perform the syncs.
+ *
+ * The slot sync worker waits for some time before the next synchronization,
+ * with the duration varying based on whether any slots were updated during
+ * the last cycle. Refer to the comments above wait_for_slot_activity() for
+ * more details.
  *
  * Any standby synchronized slots will be dropped if they no longer need
  * to be synchronized. See comment atop drop_local_obsolete_slots() for more
@@ -31,31 +38,80 @@
 
 #include "postgres.h"
 
+#include <time.h>
+
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
 #include "commands/dbcommands.h"
+#include "libpq/pqsignal.h"
+#include "pgstat.h"
+#include "postmaster/fork_process.h"
+#include "postmaster/interrupt.h"
+#include "postmaster/postmaster.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
+#include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/pg_lsn.h"
+#include "utils/ps_status.h"
+#include "utils/timeout.h"
 
-/* Struct for sharing information to control slot synchronization. */
-typedef struct SlotSyncCtxStruct
+/*
+ * Struct for sharing information to control slot synchronization.
+ *
+ * Slot sync worker's pid is needed by the startup process in order to shut it
+ * down during promotion. Startup process shuts down the slot sync worker and
+ * also sets stopSignaled=true to handle the race condition when postmaster has
+ * not noticed the promotion yet and thus may end up restarting slot sync
+ * worker. If stopSignaled is set, the worker will exit in such a case.
+ *
+ * The 'syncing' flag is needed to prevent concurrent slot syncs to avoid slot
+ * overwrites.
+ *
+ * The last_start_time is needed by postmaster to start the slot sync worker
+ * once per SLOTSYNC_RESTART_INTERVAL_SEC. In cases where a immediate restart
+ * is expected (e.g., slot sync GUCs change), slot sync worker will reset
+ * last_start_time before exiting, so that postmaster can start the worker
+ * without waiting for SLOTSYNC_RESTART_INTERVAL_SEC.
+ */
+typedef struct SlotSyncWorkerCtxStruct
 {
-	/* prevents concurrent slot syncs to avoid slot overwrites */
+	pid_t		pid;
+	bool		stopSignaled;
 	bool		syncing;
+	time_t		last_start_time;
 	slock_t		mutex;
-} SlotSyncCtxStruct;
+} SlotSyncWorkerCtxStruct;
+
+SlotSyncWorkerCtxStruct *SlotSyncWorker = NULL;
 
-SlotSyncCtxStruct *SlotSyncCtx = NULL;
+/* GUC variable */
+bool		sync_replication_slots = false;
+
+/*
+ * The sleep time (ms) between slot-sync cycles varies dynamically
+ * (within a MIN/MAX range) according to slot activity. See
+ * wait_for_slot_activity() for details.
+ */
+#define MIN_WORKER_NAPTIME_MS  200
+#define MAX_WORKER_NAPTIME_MS  30000	/* 30s */
+
+static long sleep_ms = MIN_WORKER_NAPTIME_MS;
+
+/* The restart interval for slot sync work used by postmaster */
+#define SLOTSYNC_RESTART_INTERVAL_SEC 10
+
+/* Flag to tell if we are in a slot sync worker process */
+static bool am_slotsync_worker = false;
 
 /*
  * Flag to tell if we are syncing replication slots. Unlike the 'syncing' flag
- * in SlotSyncCtxStruct, this flag is true only if the current process is
+ * in SlotSyncWorkerCtxStruct, this flag is true only if the current process is
  * performing slot synchronization.
  */
 static bool syncing_slots = false;
@@ -79,6 +135,15 @@ typedef struct RemoteSlot
 	ReplicationSlotInvalidationCause invalidated;
 } RemoteSlot;
 
+static void ProcessSlotSyncInterrupts(WalReceiverConn *wrconn, bool restart);
+
+#ifdef EXEC_BACKEND
+static pid_t slotsyncworker_forkexec(void);
+#endif
+NON_EXEC_STATIC void ReplSlotSyncWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();
+
+static void slotsync_failure_callback(int code, Datum arg);
+
 /*
  * If necessary, update the local synced slot's metadata based on the data
  * from the remote slot.
@@ -343,8 +408,11 @@ reserve_wal_for_local_slot(XLogRecPtr restart_lsn)
  * If the remote restart_lsn and catalog_xmin have caught up with the
  * local ones, then update the LSNs and persist the local synced slot for
  * future synchronization; otherwise, do nothing.
+ *
+ * Return true if the slot is marked as RS_PERSISTENT (sync-ready), otherwise
+ * false.
  */
-static void
+static bool
 update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 {
 	ReplicationSlot *slot = MyReplicationSlot;
@@ -376,7 +444,7 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 					   LSN_FORMAT_ARGS(slot->data.restart_lsn),
 					   slot->data.catalog_xmin));
 
-		return;
+		return false;
 	}
 
 	/* First time slot update, the function must return true */
@@ -388,6 +456,8 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 	ereport(LOG,
 			errmsg("newly created slot \"%s\" is sync-ready now",
 				   remote_slot->name));
+
+	return true;
 }
 
 /*
@@ -400,12 +470,15 @@ update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
  * the remote_slot catches up with locally reserved position and local slot is
  * updated. The slot is then persisted and is considered as sync-ready for
  * periodic syncs.
+ *
+ * Returns TRUE if the local slot is updated.
  */
-static void
+static bool
 synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 {
 	ReplicationSlot *slot;
 	XLogRecPtr	latestFlushPtr;
+	bool		slot_updated = false;
 
 	/*
 	 * Make sure that concerned WAL is received and flushed before syncing
@@ -413,13 +486,17 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 	 */
 	latestFlushPtr = GetStandbyFlushRecPtr(NULL);
 	if (remote_slot->confirmed_lsn > latestFlushPtr)
-		elog(ERROR,
+	{
+		elog(am_slotsync_worker ? LOG : ERROR,
 			 "skipping slot synchronization as the received slot sync"
 			 " LSN %X/%X for slot \"%s\" is ahead of the standby position %X/%X",
 			 LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
 			 remote_slot->name,
 			 LSN_FORMAT_ARGS(latestFlushPtr));
 
+		return false;
+	}
+
 	/* Search for the named slot */
 	if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
 	{
@@ -466,19 +543,22 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 			/* Make sure the invalidated state persists across server restart */
 			ReplicationSlotMarkDirty();
 			ReplicationSlotSave();
+
+			slot_updated = true;
 		}
 
 		/* Skip the sync of an invalidated slot */
 		if (slot->data.invalidated != RS_INVAL_NONE)
 		{
 			ReplicationSlotRelease();
-			return;
+			return slot_updated;
 		}
 
 		/* Slot not ready yet, let's attempt to make it sync-ready now. */
 		if (slot->data.persistency == RS_TEMPORARY)
 		{
-			update_and_persist_local_synced_slot(remote_slot, remote_dbid);
+			slot_updated = update_and_persist_local_synced_slot(remote_slot,
+																remote_dbid);
 		}
 
 		/* Slot ready for sync, so sync it. */
@@ -501,6 +581,8 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 			{
 				ReplicationSlotMarkDirty();
 				ReplicationSlotSave();
+
+				slot_updated = true;
 			}
 		}
 	}
@@ -512,7 +594,7 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 
 		/* Skip creating the local slot if remote_slot is invalidated already */
 		if (remote_slot->invalidated != RS_INVAL_NONE)
-			return;
+			return false;
 
 		/*
 		 * We create temporary slots instead of ephemeral slots here because
@@ -549,9 +631,13 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
 		LWLockRelease(ProcArrayLock);
 
 		update_and_persist_local_synced_slot(remote_slot, remote_dbid);
+
+		slot_updated = true;
 	}
 
 	ReplicationSlotRelease();
+
+	return slot_updated;
 }
 
 /*
@@ -559,8 +645,10 @@ synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid)
  *
  * Gets the failover logical slots info from the primary server and updates
  * the slots locally. Creates the slots if not present on the standby.
+ *
+ * Returns TRUE if any of the slots gets updated in this sync-cycle.
  */
-static void
+static bool
 synchronize_slots(WalReceiverConn *wrconn)
 {
 #define SLOTSYNC_COLUMN_COUNT 9
@@ -571,21 +659,30 @@ synchronize_slots(WalReceiverConn *wrconn)
 	TupleTableSlot *tupslot;
 	StringInfoData s;
 	List	   *remote_slot_list = NIL;
+	bool		some_slot_updated = false;
+	bool		started_tx = false;
 
-	SpinLockAcquire(&SlotSyncCtx->mutex);
-	if (SlotSyncCtx->syncing)
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	if (SlotSyncWorker->syncing)
 	{
-		SpinLockRelease(&SlotSyncCtx->mutex);
+		SpinLockRelease(&SlotSyncWorker->mutex);
 		ereport(ERROR,
 				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 				errmsg("cannot synchronize replication slots concurrently"));
 	}
 
-	SlotSyncCtx->syncing = true;
-	SpinLockRelease(&SlotSyncCtx->mutex);
+	SlotSyncWorker->syncing = true;
+	SpinLockRelease(&SlotSyncWorker->mutex);
 
 	syncing_slots = true;
 
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	if (!IsTransactionState())
+	{
+		StartTransactionCommand();
+		started_tx = true;
+	}
+
 	initStringInfo(&s);
 
 	/* Construct query to fetch slots with failover enabled. */
@@ -694,7 +791,7 @@ synchronize_slots(WalReceiverConn *wrconn)
 		 */
 		LockSharedObject(DatabaseRelationId, remote_dbid, 0, AccessShareLock);
 
-		synchronize_one_slot(remote_slot, remote_dbid);
+		some_slot_updated |= synchronize_one_slot(remote_slot, remote_dbid);
 
 		UnlockSharedObject(DatabaseRelationId, remote_dbid, 0, AccessShareLock);
 	}
@@ -704,21 +801,26 @@ synchronize_slots(WalReceiverConn *wrconn)
 
 	walrcv_clear_result(res);
 
-	SpinLockAcquire(&SlotSyncCtx->mutex);
-	SlotSyncCtx->syncing = false;
-	SpinLockRelease(&SlotSyncCtx->mutex);
+	if (started_tx)
+		CommitTransactionCommand();
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->syncing = false;
+	SpinLockRelease(&SlotSyncWorker->mutex);
 
 	syncing_slots = false;
+
+	return some_slot_updated;
 }
 
 /*
  * Checks the remote server info.
  *
- * We ensure that the 'primary_slot_name' exists on the remote server and the
- * remote server is not a standby node.
+ * Check whether we are a cascading standby. For non-cascading standbys, it
+ * also ensures that the 'primary_slot_name' exists on the remote server.
  */
-static void
-validate_remote_info(WalReceiverConn *wrconn)
+static bool
+validate_remote_info(WalReceiverConn *wrconn, int elevel)
 {
 #define PRIMARY_INFO_OUTPUT_COL_COUNT 2
 	WalRcvExecResult *res;
@@ -728,6 +830,7 @@ validate_remote_info(WalReceiverConn *wrconn)
 	TupleTableSlot *tupslot;
 	bool		remote_in_recovery;
 	bool		primary_slot_valid;
+	bool		started_tx = false;
 
 	initStringInfo(&cmd);
 	appendStringInfo(&cmd,
@@ -736,6 +839,13 @@ validate_remote_info(WalReceiverConn *wrconn)
 					 " WHERE slot_type='physical' AND slot_name=%s",
 					 quote_literal_cstr(PrimarySlotName));
 
+	/* The syscache access in walrcv_exec() needs a transaction env. */
+	if (!IsTransactionState())
+	{
+		StartTransactionCommand();
+		started_tx = true;
+	}
+
 	res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
 	pfree(cmd.data);
 
@@ -754,31 +864,89 @@ validate_remote_info(WalReceiverConn *wrconn)
 	Assert(!isnull);
 
 	if (remote_in_recovery)
-		ereport(ERROR,
+	{
+		/*
+		 * If we are a cascading standby, no need to check further for
+		 * 'primary_slot_name'.
+		 */
+		ereport(elevel,
 				errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				errmsg("cannot synchronize replication slots from a standby server"));
+	}
+	else
+	{
+		primary_slot_valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
+		Assert(!isnull);
 
-	primary_slot_valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
-	Assert(!isnull);
-
-	if (!primary_slot_valid)
-		ereport(ERROR,
-				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				errmsg("bad configuration for slot synchronization"),
-		/* translator: second %s is a GUC variable name */
-				errdetail("The replication slot \"%s\" specified by \"%s\" does not exist on the primary server.",
-						  PrimarySlotName, "primary_slot_name"));
+		if (!primary_slot_valid)
+			ereport(elevel,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("bad configuration for slot synchronization"),
+			/* translator: second %s is a GUC variable name */
+					errdetail("The replication slot \"%s\" specified by \"%s\" does not exist on the primary server.",
+							  PrimarySlotName, "primary_slot_name"));
+	}
 
 	ExecClearTuple(tupslot);
 	walrcv_clear_result(res);
+
+	if (started_tx)
+		CommitTransactionCommand();
+
+	return (!remote_in_recovery && primary_slot_valid);
 }
 
 /*
- * Check all necessary GUCs for slot synchronization are set
- * appropriately, otherwise, raise ERROR.
+ * Check that we are not a cascading standby and 'primary_slot_name' exists
+ * on the remote server. If not, sleep for MAX_WORKER_NAPTIME_MS and check
+ * again. The idea is to become no-op until we get valid remote info.
  */
-void
-ValidateSlotSyncParams(void)
+static void
+ensure_valid_remote_info(WalReceiverConn *wrconn)
+{
+	int			rc;
+
+	for (;;)
+	{
+		if (validate_remote_info(wrconn, LOG))
+			break;
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   MAX_WORKER_NAPTIME_MS,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		ProcessSlotSyncInterrupts(NULL, true);
+	}
+}
+
+/*
+ * Get database name from the conninfo.
+ *
+ * If dbname is extracted already from the conninfo, just return it.
+ */
+static char *
+get_dbname_from_conninfo(const char *conninfo)
+{
+	static char *dbname;
+
+	if (dbname)
+		return dbname;
+	else
+		dbname = walrcv_get_dbname_from_conninfo(conninfo);
+
+	return dbname;
+}
+
+/*
+ * Return true if all necessary GUCs for slot synchronization are set
+ * appropriately, otherwise, return false.
+ */
+bool
+ValidateSlotSyncParams(int elevel)
 {
 	char	   *dbname;
 
@@ -789,11 +957,14 @@ ValidateSlotSyncParams(void)
 	 * be invalidated.
 	 */
 	if (PrimarySlotName == NULL || *PrimarySlotName == '\0')
-		ereport(ERROR,
+	{
+		ereport(elevel,
 		/* translator: %s is a GUC variable name */
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("bad configuration for slot synchronization"),
 				errhint("\"%s\" must be defined.", "primary_slot_name"));
+		return false;
+	}
 
 	/*
 	 * hot_standby_feedback must be enabled to cooperate with the physical
@@ -801,37 +972,47 @@ ValidateSlotSyncParams(void)
 	 * catalog_xmin values on the standby.
 	 */
 	if (!hot_standby_feedback)
-		ereport(ERROR,
+	{
+		ereport(elevel,
 		/* translator: %s is a GUC variable name */
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("bad configuration for slot synchronization"),
 				errhint("\"%s\" must be enabled.", "hot_standby_feedback"));
+		return false;
+	}
 
 	/* Logical slot sync/creation requires wal_level >= logical. */
 	if (wal_level < WAL_LEVEL_LOGICAL)
-		ereport(ERROR,
+	{
+		ereport(elevel,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("bad configuration for slot synchronization"),
 				errhint("\"wal_level\" must be >= logical."));
+		return false;
+	}
 
 	/*
 	 * The primary_conninfo is required to make connection to primary for
 	 * getting slots information.
 	 */
 	if (PrimaryConnInfo == NULL || *PrimaryConnInfo == '\0')
-		ereport(ERROR,
+	{
+		ereport(elevel,
 		/* translator: %s is a GUC variable name */
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("bad configuration for slot synchronization"),
 				errhint("\"%s\" must be defined.", "primary_conninfo"));
+		return false;
+	}
 
 	/*
 	 * The slot synchronization needs a database connection for walrcv_exec to
 	 * work.
 	 */
-	dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
+	dbname = get_dbname_from_conninfo(PrimaryConnInfo);
 	if (dbname == NULL)
-		ereport(ERROR,
+	{
+		ereport(elevel,
 
 		/*
 		 * translator: 'dbname' is a specific option; %s is a GUC variable
@@ -840,10 +1021,491 @@ ValidateSlotSyncParams(void)
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("bad configuration for slot synchronization"),
 				errhint("'dbname' must be specified in \"%s\".", "primary_conninfo"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Check that all necessary GUCs for slot synchronization are set
+ * appropriately. If not, sleep for MAX_WORKER_NAPTIME_MS and check again.
+ * The idea is to become no-op until we get valid GUCs values.
+ */
+static void
+ensure_valid_slotsync_params(void)
+{
+	int			rc;
+
+	/* Sanity check. */
+	Assert(sync_replication_slots);
+
+	for (;;)
+	{
+		if (ValidateSlotSyncParams(LOG))
+		{
+			ereport(LOG, errmsg("parameters validation done for slot synchronization"));
+			break;
+		}
+
+		ereport(LOG, errmsg("skipping slot synchronization"));
+
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   MAX_WORKER_NAPTIME_MS,
+					   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+		if (rc & WL_LATCH_SET)
+			ResetLatch(MyLatch);
+
+		/*
+		 * Process the interrupts. Pass 'restart' as false as we do not want
+		 * the worker to be restarted on GUC change.
+		 */
+		ProcessSlotSyncInterrupts(NULL, false);
+	}
+}
+
+/*
+ * Re-read the config file.
+ *
+ * If any of the slot sync GUCs have changed, exit the worker and
+ * let it get restarted by the postmaster. The worker to be exited for
+ * restart purpose only if the caller passed restart as true.
+ */
+static void
+slotsync_reread_config(bool restart)
+{
+	char	   *old_primary_conninfo = pstrdup(PrimaryConnInfo);
+	char	   *old_primary_slotname = pstrdup(PrimarySlotName);
+	bool		old_sync_replication_slots = sync_replication_slots;
+	bool		old_hot_standby_feedback = hot_standby_feedback;
+	bool		conninfo_changed;
+	bool		primary_slotname_changed;
+
+	Assert(sync_replication_slots);
+
+	ConfigReloadPending = false;
+	ProcessConfigFile(PGC_SIGHUP);
+
+	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
+	primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
+	pfree(old_primary_conninfo);
+	pfree(old_primary_slotname);
+
+	if (old_sync_replication_slots != sync_replication_slots)
+	{
+		ereport(LOG,
+		/* translator: %s is a GUC variable name */
+				errmsg("slot sync worker will shutdown because %s is disabled", "sync_replication_slots"));
+		proc_exit(0);
+	}
+
+	/* The caller instructed to skip restart */
+	if (!restart)
+		return;
+
+	if (conninfo_changed ||
+		primary_slotname_changed ||
+		(old_hot_standby_feedback != hot_standby_feedback))
+	{
+		ereport(LOG,
+				errmsg("slot sync worker will restart because of a parameter change"));
+
+		/*
+		 * Reset the last-start time for this worker so that the postmaster
+		 * can restart it without waiting for SLOTSYNC_RESTART_INTERVAL_SEC.
+		 */
+		SlotSyncWorker->last_start_time = 0;
+
+		proc_exit(0);
+	}
+
 }
 
 /*
- * Is current process syncing replication slots ?
+ * Interrupt handler for main loop of slot sync worker.
+ */
+static void
+ProcessSlotSyncInterrupts(WalReceiverConn *wrconn, bool restart)
+{
+	CHECK_FOR_INTERRUPTS();
+
+	if (ShutdownRequestPending)
+	{
+		ereport(LOG,
+				errmsg("replication slot sync worker is shutting down on receiving SIGINT"));
+
+		proc_exit(0);
+	}
+
+	if (ConfigReloadPending)
+		slotsync_reread_config(restart);
+}
+
+/*
+ * Cleanup function for slotsync worker.
+ *
+ * Called on slotsync worker exit.
+ */
+static void
+slotsync_worker_onexit(int code, Datum arg)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	SlotSyncWorker->pid = InvalidPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * Sleep for long enough that we believe it's likely that the slots on primary
+ * get updated.
+ *
+ * If there is no slot activity the wait time between sync-cycles will double
+ * (to a maximum of 30s). If there is some slot activity the wait time between
+ * sync-cycles is reset to the minimum (200ms).
+ */
+static void
+wait_for_slot_activity(bool some_slot_updated)
+{
+	int			rc;
+
+	if (!some_slot_updated)
+	{
+		/*
+		 * No slots were updated, so double the sleep time, but not beyond the
+		 * maximum allowable value.
+		 */
+		sleep_ms = Min(sleep_ms * 2, MAX_WORKER_NAPTIME_MS);
+	}
+	else
+	{
+		/*
+		 * Some slots were updated since the last sleep, so reset the sleep
+		 * time.
+		 */
+		sleep_ms = MIN_WORKER_NAPTIME_MS;
+	}
+
+	rc = WaitLatch(MyLatch,
+				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+				   sleep_ms,
+				   WAIT_EVENT_REPL_SLOTSYNC_MAIN);
+
+	if (rc & WL_LATCH_SET)
+		ResetLatch(MyLatch);
+}
+
+/*
+ * The main loop of our worker process.
+ *
+ * It connects to the primary server, fetches logical failover slots
+ * information periodically in order to create and sync the slots.
+ */
+NON_EXEC_STATIC void
+ReplSlotSyncWorkerMain(int argc, char *argv[])
+{
+	WalReceiverConn *wrconn = NULL;
+	char	   *dbname;
+	char	   *err;
+	sigjmp_buf	local_sigjmp_buf;
+	StringInfoData app_name;
+
+	am_slotsync_worker = true;
+
+	MyBackendType = B_SLOTSYNC_WORKER;
+
+	init_ps_display(NULL);
+
+	SetProcessingMode(InitProcessing);
+
+	/*
+	 * Create a per-backend PGPROC struct in shared memory.  We must do this
+	 * before we access any shared memory.
+	 */
+	InitProcess();
+
+	/*
+	 * Early initialization.
+	 */
+	BaseInit();
+
+	Assert(SlotSyncWorker != NULL);
+
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+	Assert(SlotSyncWorker->pid == InvalidPid);
+
+	/*
+	 * Startup process signaled the slot sync worker to stop, so if meanwhile
+	 * postmaster ended up starting the worker again, exit.
+	 */
+	if (SlotSyncWorker->stopSignaled)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		proc_exit(0);
+	}
+
+	/* Advertise our PID so that the startup process can kill us on promotion */
+	SlotSyncWorker->pid = MyProcPid;
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	ereport(LOG, errmsg("replication slot sync worker started"));
+
+	on_shmem_exit(slotsync_worker_onexit, (Datum) 0);
+
+	/* Setup signal handling */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, die);
+	pqsignal(SIGFPE, FloatExceptionHandler);
+	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR2, SIG_IGN);
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGCHLD, SIG_DFL);
+
+	/*
+	 * Establishes SIGALRM handler and initialize timeout module. It is needed
+	 * by InitPostgres to register different timeouts.
+	 */
+	InitializeTimeouts();
+
+	/* Load the libpq-specific functions */
+	load_file("libpqwalreceiver", false);
+
+	/*
+	 * If an exception is encountered, processing resumes here.
+	 *
+	 * We just need to clean up, report the error, and go away.
+	 *
+	 * If we do not have this handling here, then since this worker process
+	 * operates at the bottom of the exception stack, ERRORs turn into FATALs.
+	 * Therefore, we create our own exception handler to catch ERRORs.
+	 */
+	if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+	{
+		/* since not using PG_TRY, must reset error stack by hand */
+		error_context_stack = NULL;
+
+		/* Prevents interrupts while cleaning up */
+		HOLD_INTERRUPTS();
+
+		/* Report the error to the server log */
+		EmitErrorReport();
+
+		/*
+		 * We can now go away.  Note that because we called InitProcess, a
+		 * callback was registered to do ProcKill, which will clean up
+		 * necessary state.
+		 */
+		proc_exit(0);
+	}
+
+	/* We can now handle ereport(ERROR) */
+	PG_exception_stack = &local_sigjmp_buf;
+
+	/*
+	 * Unblock signals (they were blocked when the postmaster forked us)
+	 */
+	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+
+	ensure_valid_slotsync_params();
+
+	dbname = get_dbname_from_conninfo(PrimaryConnInfo);
+
+	/*
+	 * Connect to the database specified by user in primary_conninfo. We need
+	 * a database connection for walrcv_exec to work. Please see comments atop
+	 * libpqrcv_exec.
+	 */
+	InitPostgres(dbname, InvalidOid, NULL, InvalidOid, 0, NULL);
+
+	SetProcessingMode(NormalProcessing);
+
+	initStringInfo(&app_name);
+	if (cluster_name[0])
+		appendStringInfo(&app_name, "%s_%s", cluster_name, "slotsyncworker");
+	else
+		appendStringInfo(&app_name, "%s", "slotsyncworker");
+
+	/*
+	 * Establish the connection to the primary server for slots
+	 * synchronization.
+	 */
+	wrconn = walrcv_connect(PrimaryConnInfo, false, false, false,
+							app_name.data,
+							&err);
+	pfree(app_name.data);
+
+	if (!wrconn)
+		ereport(ERROR,
+				errcode(ERRCODE_CONNECTION_FAILURE),
+				errmsg("could not connect to the primary server: %s", err));
+
+	before_shmem_exit(slotsync_failure_callback, PointerGetDatum(wrconn));
+
+	/*
+	 * Using the specified primary server connection, ensure that we are not a
+	 * cascading standby and slot configured in 'primary_slot_name' exists on
+	 * the primary server.
+	 */
+	ensure_valid_remote_info(wrconn);
+
+	/* Main wait loop */
+	for (;;)
+	{
+		bool		some_slot_updated = false;
+
+		ProcessSlotSyncInterrupts(wrconn, true);
+
+		some_slot_updated = synchronize_slots(wrconn);
+
+		wait_for_slot_activity(some_slot_updated);
+	}
+
+	/*
+	 * The slot sync worker can not get here because it will only stop when it
+	 * receives a SIGINT from the startup process, or when there is an error.
+	 */
+	Assert(false);
+}
+
+/*
+ * Main entry point for slot sync worker process, to be called from the
+ * postmaster.
+ */
+int
+StartSlotSyncWorker(void)
+{
+	pid_t		pid;
+
+#ifdef EXEC_BACKEND
+	switch ((pid = slotsyncworker_forkexec()))
+	{
+#else
+	switch ((pid = fork_process()))
+	{
+		case 0:
+			/* in postmaster child ... */
+			InitPostmasterChild();
+
+			/* Close the postmaster's sockets */
+			ClosePostmasterPorts(false);
+
+			ReplSlotSyncWorkerMain(0, NULL);
+			break;
+#endif
+		case -1:
+			ereport(LOG,
+					(errmsg("could not fork slot sync worker process: %m")));
+			return 0;
+
+		default:
+			return (int) pid;
+	}
+
+	/* shouldn't get here */
+	return 0;
+}
+
+#ifdef EXEC_BACKEND
+/*
+ * The forkexec routine for the slot sync worker process.
+ *
+ * Format up the arglist, then fork and exec.
+ */
+static pid_t
+slotsyncworker_forkexec(void)
+{
+	char	   *av[10];
+	int			ac = 0;
+
+	av[ac++] = "postgres";
+	av[ac++] = "--forkssworker";
+	av[ac++] = NULL;			/* filled in by postmaster_forkexec */
+	av[ac] = NULL;
+
+	Assert(ac < lengthof(av));
+
+	return postmaster_forkexec(ac, av);
+}
+#endif
+
+/*
+ * Shut down the slot sync worker.
+ */
+void
+ShutDownSlotSync(void)
+{
+	SpinLockAcquire(&SlotSyncWorker->mutex);
+
+	SlotSyncWorker->stopSignaled = true;
+
+	if (SlotSyncWorker->pid == InvalidPid)
+	{
+		SpinLockRelease(&SlotSyncWorker->mutex);
+		return;
+	}
+	SpinLockRelease(&SlotSyncWorker->mutex);
+
+	kill(SlotSyncWorker->pid, SIGINT);
+
+	/* Wait for it to die */
+	for (;;)
+	{
+		int			rc;
+
+		/* Wait a bit, we don't expect to have to wait long */
+		rc = WaitLatch(MyLatch,
+					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   10L, WAIT_EVENT_REPL_SLOTSYNC_SHUTDOWN);
+
+		if (rc & WL_LATCH_SET)
+		{
+			ResetLatch(MyLatch);
+			CHECK_FOR_INTERRUPTS();
+		}
+
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+
+		/* Is it gone? */
+		if (SlotSyncWorker->pid == InvalidPid)
+			break;
+
+		SpinLockRelease(&SlotSyncWorker->mutex);
+	}
+
+	SpinLockRelease(&SlotSyncWorker->mutex);
+}
+
+/*
+ * SlotSyncWorkerCanRestart
+ *
+ * Returns true if enough time has passed (SLOTSYNC_RESTART_INTERVAL_SEC)
+ * since it was launched last. Otherwise returns false.
+ *
+ * This is a safety valve to protect against continuous respawn attempts if the
+ * worker is dying immediately at launch. Note that since we will retry to
+ * launch the worker from the postmaster main loop, we will get another
+ * chance later.
+ */
+bool
+SlotSyncWorkerCanRestart(void)
+{
+	time_t		curtime = time(NULL);
+
+	/* Return false if too soon since last start. */
+	if ((unsigned int) (curtime - SlotSyncWorker->last_start_time) <
+		(unsigned int) SLOTSYNC_RESTART_INTERVAL_SEC)
+		return false;
+
+	SlotSyncWorker->last_start_time = curtime;
+
+	return true;
+}
+
+/*
+ * Is current process syncing replication slots?
+ *
+ * Could be either backend executing SQL function or slot sync worker.
  */
 bool
 IsSyncingReplicationSlots(void)
@@ -851,30 +1513,41 @@ IsSyncingReplicationSlots(void)
 	return syncing_slots;
 }
 
+/*
+ * Is current process a slot sync worker?
+ */
+bool
+IsLogicalSlotSyncWorker(void)
+{
+	return am_slotsync_worker;
+}
+
 /*
  * Amount of shared memory required for slot synchronization.
  */
 Size
-SlotSyncShmemSize(void)
+SlotSyncWorkerShmemSize(void)
 {
-	return sizeof(SlotSyncCtxStruct);
+	return sizeof(SlotSyncWorkerCtxStruct);
 }
 
 /*
  * Allocate and initialize the shared memory of slot synchronization.
  */
 void
-SlotSyncShmemInit(void)
+SlotSyncWorkerShmemInit(void)
 {
+	Size		size = SlotSyncWorkerShmemSize();
 	bool		found;
 
-	SlotSyncCtx = (SlotSyncCtxStruct *)
-		ShmemInitStruct("Slot Sync Data", SlotSyncShmemSize(), &found);
+	SlotSyncWorker = (SlotSyncWorkerCtxStruct *)
+		ShmemInitStruct("Slot Sync Worker Data", size, &found);
 
 	if (!found)
 	{
-		SlotSyncCtx->syncing = false;
-		SpinLockInit(&SlotSyncCtx->mutex);
+		memset(SlotSyncWorker, 0, size);
+		SlotSyncWorker->pid = InvalidPid;
+		SpinLockInit(&SlotSyncWorker->mutex);
 	}
 }
 
@@ -893,9 +1566,9 @@ slotsync_failure_callback(int code, Datum arg)
 		 * without resetting the flag. So, we need to clean up shared memory
 		 * and reset the flag here.
 		 */
-		SpinLockAcquire(&SlotSyncCtx->mutex);
-		SlotSyncCtx->syncing = false;
-		SpinLockRelease(&SlotSyncCtx->mutex);
+		SpinLockAcquire(&SlotSyncWorker->mutex);
+		SlotSyncWorker->syncing = false;
+		SpinLockRelease(&SlotSyncWorker->mutex);
 
 		syncing_slots = false;
 	}
@@ -912,7 +1585,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 {
 	PG_ENSURE_ERROR_CLEANUP(slotsync_failure_callback, PointerGetDatum(wrconn));
 	{
-		validate_remote_info(wrconn);
+		validate_remote_info(wrconn, ERROR);
 
 		synchronize_slots(wrconn);
 	}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 2180a38063..dd4ea0388a 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -1236,6 +1236,20 @@ restart:
 		 * concurrently being dropped by a backend connected to another DB.
 		 *
 		 * That's fairly unlikely in practice, so we'll just bail out.
+		 *
+		 * The slot sync worker holds a shared lock on the database before
+		 * operating on synced logical slots to avoid conflict with the drop
+		 * happening here. The persistent synced slots are thus safe but there
+		 * is a possibility that the slot sync worker has created a temporary
+		 * slot (which stays active even on release) and we are trying to drop
+		 * the same here. In practice, the chances of hitting this scenario is
+		 * very less as during slot synchronization, the temporary slot is
+		 * immediately converted to persistent and thus is safe due to the
+		 * shared lock taken on the database. So for the time being, we'll
+		 * just bail out in such a scenario.
+		 *
+		 * XXX: If needed, we can consider shutting down slot sync worker
+		 * before trying to drop synced temporary slots here.
 		 */
 		if (active_pid)
 			ereport(ERROR,
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index d2fa5e669a..c4a0bf99ee 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -975,7 +975,7 @@ pg_sync_replication_slots(PG_FUNCTION_ARGS)
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 
-	ValidateSlotSyncParams();
+	ValidateSlotSyncParams(ERROR);
 
 	initStringInfo(&app_name);
 	if (cluster_name[0])
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index e5477c1de1..b33044e10f 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3389,7 +3389,7 @@ WalSndDone(WalSndSendDataCallback send_data)
  * This should only be called when in recovery.
  *
  * This is called either by cascading walsender to find WAL postion to be sent
- * to a cascaded standby or by slot synchronization function to validate remote
+ * to a cascaded standby or by slot synchronization operation to validate remote
  * slot's lsn before syncing it locally.
  *
  * As a side-effect, *tli is updated to the TLI of the last
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 7e7941d625..8ecd89f8ea 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -154,7 +154,7 @@ CalculateShmemSize(int *num_semaphores)
 	size = add_size(size, StatsShmemSize());
 	size = add_size(size, WaitEventExtensionShmemSize());
 	size = add_size(size, InjectionPointShmemSize());
-	size = add_size(size, SlotSyncShmemSize());
+	size = add_size(size, SlotSyncWorkerShmemSize());
 #ifdef EXEC_BACKEND
 	size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -349,7 +349,7 @@ CreateOrAttachShmemStructs(void)
 	WalSummarizerShmemInit();
 	PgArchShmemInit();
 	ApplyLauncherShmemInit();
-	SlotSyncShmemInit();
+	SlotSyncWorkerShmemInit();
 
 	/*
 	 * Set up other modules that need some shared memory space
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 1afcbfc052..ac83331bbc 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -40,6 +40,7 @@
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "replication/slot.h"
+#include "replication/slotsync.h"
 #include "replication/syncrep.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
@@ -365,8 +366,12 @@ InitProcess(void)
 	 * child; this is so that the postmaster can detect it if we exit without
 	 * cleaning up.  (XXX autovac launcher currently doesn't participate in
 	 * this; it probably should.)
+	 *
+	 * Slot sync worker also does not participate in it, see comments atop
+	 * 'struct bkend' in postmaster.c.
 	 */
-	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
+	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess() &&
+		!IsLogicalSlotSyncWorker())
 		MarkPostmasterChildActive();
 
 	/*
@@ -935,8 +940,12 @@ ProcKill(int code, Datum arg)
 	 * This process is no longer present in shared memory in any meaningful
 	 * way, so tell the postmaster we've cleaned up acceptably well. (XXX
 	 * autovac launcher should be included here someday)
+	 *
+	 * Slot sync worker is also not a postmaster child, so skip this shared
+	 * memory related processing here.
 	 */
-	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess())
+	if (IsUnderPostmaster && !IsAutoVacuumLauncherProcess() &&
+		!IsLogicalSlotSyncWorker())
 		MarkPostmasterChildInactive();
 
 	/* wake autovac launcher if needed -- see comments in FreeWorkerInfo */
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index 43c393d6fe..9d6e067382 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -338,6 +338,7 @@ pgstat_tracks_io_bktype(BackendType bktype)
 		case B_BG_WORKER:
 		case B_BG_WRITER:
 		case B_CHECKPOINTER:
+		case B_SLOTSYNC_WORKER:
 		case B_STANDALONE_BACKEND:
 		case B_STARTUP:
 		case B_WAL_SENDER:
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 6464386b77..b52afd4eac 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -53,6 +53,8 @@ LOGICAL_APPLY_MAIN	"Waiting in main loop of logical replication apply process."
 LOGICAL_LAUNCHER_MAIN	"Waiting in main loop of logical replication launcher process."
 LOGICAL_PARALLEL_APPLY_MAIN	"Waiting in main loop of logical replication parallel apply process."
 RECOVERY_WAL_STREAM	"Waiting in main loop of startup process for WAL to arrive, during streaming recovery."
+REPL_SLOTSYNC_MAIN	"Waiting in main loop of slot sync worker."
+REPL_SLOTSYNC_SHUTDOWN	"Waiting for slot sync worker to shut down."
 SYSLOGGER_MAIN	"Waiting in main loop of syslogger process."
 WAL_RECEIVER_MAIN	"Waiting in main loop of WAL receiver process."
 WAL_SENDER_MAIN	"Waiting in main loop of WAL sender process."
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 23f77a59e5..26aaf292c5 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -40,6 +40,7 @@
 #include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
+#include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
@@ -293,6 +294,9 @@ GetBackendTypeDesc(BackendType backendType)
 		case B_LOGGER:
 			backendDesc = "logger";
 			break;
+		case B_SLOTSYNC_WORKER:
+			backendDesc = "slotsyncworker";
+			break;
 		case B_STANDALONE_BACKEND:
 			backendDesc = "standalone backend";
 			break;
@@ -835,9 +839,10 @@ InitializeSessionUserIdStandalone(void)
 {
 	/*
 	 * This function should only be called in single-user mode, in autovacuum
-	 * workers, and in background workers.
+	 * workers, in slot sync worker and in background workers.
 	 */
-	Assert(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsBackgroundWorker);
+	Assert(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() ||
+		   IsLogicalSlotSyncWorker() || IsBackgroundWorker);
 
 	/* call only once */
 	Assert(!OidIsValid(AuthenticatedUserId));
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 7797876d00..5ffe9bdd98 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -43,6 +43,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/postmaster.h"
 #include "replication/slot.h"
+#include "replication/slotsync.h"
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -876,10 +877,11 @@ InitPostgres(const char *in_dbname, Oid dboid,
 	 * Perform client authentication if necessary, then figure out our
 	 * postgres user ID, and see if we are a superuser.
 	 *
-	 * In standalone mode and in autovacuum worker processes, we use a fixed
-	 * ID, otherwise we figure it out from the authenticated user name.
+	 * In standalone mode, autovacuum worker processes and slot sync worker
+	 * process, we use a fixed ID, otherwise we figure it out from the
+	 * authenticated user name.
 	 */
-	if (bootstrap || IsAutoVacuumWorkerProcess())
+	if (bootstrap || IsAutoVacuumWorkerProcess() || IsLogicalSlotSyncWorker())
 	{
 		InitializeSessionUserIdStandalone();
 		am_superuser = true;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 70652f0a3f..37be0669bb 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
 #include "postmaster/walwriter.h"
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
+#include "replication/slotsync.h"
 #include "replication/syncrep.h"
 #include "storage/bufmgr.h"
 #include "storage/large_object.h"
@@ -2054,6 +2055,15 @@ struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"sync_replication_slots", PGC_SIGHUP, REPLICATION_STANDBY,
+			gettext_noop("Enables a physical standby to synchronize logical failover slots from the primary server."),
+		},
+		&sync_replication_slots,
+		false,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e10755972a..c97f9a25f0 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -361,6 +361,7 @@
 #wal_retrieve_retry_interval = 5s	# time to wait before retrying to
 					# retrieve WAL after a failed attempt
 #recovery_min_apply_delay = 0		# minimum delay for applying changes during recovery
+#sync_replication_slots = off			# enables slot synchronization on the physical standby from the primary
 
 # - Subscribers -
 
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0445fbf61d..612fb5f42e 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -333,6 +333,7 @@ typedef enum BackendType
 	B_BG_WRITER,
 	B_CHECKPOINTER,
 	B_LOGGER,
+	B_SLOTSYNC_WORKER,
 	B_STANDALONE_BACKEND,
 	B_STARTUP,
 	B_WAL_RECEIVER,
diff --git a/src/include/replication/slotsync.h b/src/include/replication/slotsync.h
index e86d8a47b8..aa688a2f1b 100644
--- a/src/include/replication/slotsync.h
+++ b/src/include/replication/slotsync.h
@@ -14,10 +14,28 @@
 
 #include "replication/walreceiver.h"
 
-extern void ValidateSlotSyncParams(void);
+extern PGDLLIMPORT bool sync_replication_slots;
+
+/*
+ * GUCs needed by slot sync worker to connect to the primary
+ * server and carry on with slots synchronization.
+ */
+extern PGDLLIMPORT char *PrimaryConnInfo;
+extern PGDLLIMPORT char *PrimarySlotName;
+
+extern bool ValidateSlotSyncParams(int elevel);
+
+#ifdef EXEC_BACKEND
+extern void ReplSlotSyncWorkerMain(int argc, char *argv[]) pg_attribute_noreturn();
+#endif
+extern int	StartSlotSyncWorker(void);
+
+extern void ShutDownSlotSync(void);
+extern bool SlotSyncWorkerCanRestart(void);
 extern bool IsSyncingReplicationSlots(void);
-extern Size SlotSyncShmemSize(void);
-extern void SlotSyncShmemInit(void);
+extern bool IsLogicalSlotSyncWorker(void);
+extern Size SlotSyncWorkerShmemSize(void);
+extern void SlotSyncWorkerShmemInit(void);
 extern void SyncReplicationSlots(WalReceiverConn *wrconn);
 
 #endif							/* SLOTSYNC_H */
diff --git a/src/test/recovery/t/040_standby_failover_slots_sync.pl b/src/test/recovery/t/040_standby_failover_slots_sync.pl
index 2755c3fc84..950f6b3738 100644
--- a/src/test/recovery/t/040_standby_failover_slots_sync.pl
+++ b/src/test/recovery/t/040_standby_failover_slots_sync.pl
@@ -22,7 +22,8 @@ $publisher->append_conf('postgresql.conf', 'autovacuum = off');
 $publisher->start;
 
 $publisher->safe_psql('postgres',
-	"CREATE PUBLICATION regress_mypub FOR ALL TABLES;");
+	"CREATE PUBLICATION regress_mypub FOR ALL TABLES;"
+);
 
 my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
 
@@ -322,6 +323,10 @@ ok( $stderr =~
 	  /HINT:  'dbname' must be specified in "primary_conninfo"/,
 	"cannot sync slots if dbname is not specified in primary_conninfo");
 
+# Add the dbname back to the primary_conninfo for further tests
+$standby1->append_conf('postgresql.conf', "primary_conninfo = '$connstr_1 dbname=postgres'");
+$standby1->reload;
+
 ##################################################
 # Test that we cannot synchronize slots to a cascading standby server.
 ##################################################
@@ -355,4 +360,110 @@ ok( $stderr =~
 	  /ERROR:  cannot synchronize replication slots from a standby server/,
 	"cannot sync slots to a cascading standby server");
 
+$cascading_standby->stop;
+
+##################################################
+# Test to confirm that the slot sync worker waits until it gets valid values
+# for all the slot sync related GUCs
+##################################################
+
+$log_offset = -s $standby1->logfile;
+
+# Enable slot sync worker but disable another GUC required for slot sync
+$standby1->append_conf(
+	'postgresql.conf', qq(
+sync_replication_slots = on
+hot_standby_feedback = off
+));
+$standby1->reload;
+
+# Confirm that the slot sync worker logs bad configuration msg
+$standby1->wait_for_log(qr/LOG:  bad configuration for slot synchronization/,
+	$log_offset);
+
+$log_offset = -s $standby1->logfile;
+
+$standby1->append_conf('postgresql.conf', "hot_standby_feedback = on");
+$standby1->reload;
+
+# Confirm that the slot sync worker proceeds once the configuration is correct.
+$standby1->wait_for_log(qr/LOG:  parameters validation done for slot synchronization/,
+	$log_offset);
+
+##################################################
+# Test to confirm that restart_lsn and confirmed_flush_lsn of the logical slot
+# on the primary is synced to the standby via the slot sync worker.
+##################################################
+
+# Insert data on the primary
+$primary->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	INSERT INTO tab_int SELECT generate_series(1, 10);
+]);
+
+# Subscribe to the new table data and wait for it to arrive
+$subscriber1->safe_psql(
+	'postgres', qq[
+	CREATE TABLE tab_int (a int PRIMARY KEY);
+	ALTER SUBSCRIPTION regress_mysub1 ENABLE;
+	ALTER SUBSCRIPTION regress_mysub1 REFRESH PUBLICATION;
+]);
+
+$subscriber1->wait_for_subscription_sync;
+
+# Do not allow any further advancement of the restart_lsn and
+# confirmed_flush_lsn for the lsub1_slot.
+$subscriber1->safe_psql('postgres', "ALTER SUBSCRIPTION regress_mysub1 DISABLE");
+
+# Wait for the replication slot to become inactive on the publisher
+$primary->poll_query_until(
+	'postgres',
+	"SELECT COUNT(*) FROM pg_catalog.pg_replication_slots WHERE slot_name = 'lsub1_slot' AND active='f'",
+	1);
+
+# Get the restart_lsn for the logical slot lsub1_slot on the primary
+my $primary_restart_lsn = $primary->safe_psql('postgres',
+	"SELECT restart_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Get the confirmed_flush_lsn for the logical slot lsub1_slot on the primary
+my $primary_flush_lsn = $primary->safe_psql('postgres',
+	"SELECT confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';");
+
+# Confirm that restart_lsn and of confirmed_flush_lsn lsub1_slot slot are synced
+# to the standby
+ok( $standby1->poll_query_until(
+		'postgres',
+		"SELECT '$primary_restart_lsn' = restart_lsn AND '$primary_flush_lsn' = confirmed_flush_lsn from pg_replication_slots WHERE slot_name = 'lsub1_slot';"),
+	'restart_lsn and confirmed_flush_lsn of slot lsub1_slot synced to standby');
+
+##################################################
+# Promote the standby1 to primary. Confirm that:
+# a) the slot 'lsub1_slot' is retained on the new primary
+# b) logical replication for regress_mysub1 is resumed successfully after failover
+##################################################
+$standby1->promote;
+
+# Update subscription with the new primary's connection info
+my $standby1_conninfo = $standby1->connstr . ' dbname=postgres';
+$subscriber1->safe_psql('postgres',
+	"ALTER SUBSCRIPTION regress_mysub1 CONNECTION '$standby1_conninfo';
+	 ALTER SUBSCRIPTION regress_mysub1 ENABLE; ");
+
+# Confirm the synced slot 'lsub1_slot' is retained on the new primary
+is($standby1->safe_psql('postgres',
+	q{SELECT slot_name FROM pg_replication_slots WHERE slot_name = 'lsub1_slot';}),
+	'lsub1_slot',
+	'synced slot retained on the new primary');
+
+# Insert data on the new primary
+$standby1->safe_psql('postgres',
+	"INSERT INTO tab_int SELECT generate_series(11, 20);");
+$standby1->wait_for_catchup('regress_mysub1');
+
+# Confirm that data in tab_int replicated on the subscriber
+is( $subscriber1->safe_psql('postgres', q{SELECT count(*) FROM tab_int;}),
+	"20",
+	'data replicated from the new primary');
+
 done_testing();
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index d808aad8b0..a35e399f0c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2585,7 +2585,7 @@ SlabBlock
 SlabContext
 SlabSlot
 SlotNumber
-SlotSyncCtxStruct
+SlotSyncWorkerCtxStruct
 SlruCtl
 SlruCtlData
 SlruErrorCause
-- 
2.34.1

