Coverage Report

Created: 2026-11-12 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/replication/logical/slotsync.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 * slotsync.c
3
 *     Functionality for synchronizing slots to a standby server from the
4
 *         primary server.
5
 *
6
 * Copyright (c) 2024-2026, PostgreSQL Global Development Group
7
 *
8
 * IDENTIFICATION
9
 *    src/backend/replication/logical/slotsync.c
10
 *
11
 * This file contains the code for slot synchronization on a physical standby
12
 * to fetch logical failover slots information from the primary server, create
13
 * the slots on the standby and synchronize them periodically.
14
 *
15
 * Slot synchronization can be performed either automatically by enabling slot
16
 * sync worker or manually by calling SQL function pg_sync_replication_slots().
17
 *
18
 * If the WAL corresponding to the remote's restart_lsn is not available on the
19
 * physical standby or the remote's catalog_xmin precedes the oldest xid for
20
 * which it is guaranteed that rows wouldn't have been removed then we cannot
21
 * create the local standby slot because that would mean moving the local slot
22
 * backward and decoding won't be possible via such a slot. In this case, the
23
 * slot will be marked as RS_TEMPORARY. Once the primary server catches up,
24
 * the slot will be marked as RS_PERSISTENT (which means sync-ready) after
25
 * which slot sync worker can perform the sync periodically or user can call
26
 * pg_sync_replication_slots() periodically to perform the syncs.
27
 *
28
 * If synchronized slots fail to build a consistent snapshot from the
29
 * restart_lsn before reaching confirmed_flush_lsn, they would become
30
 * unreliable after promotion due to potential data loss from changes
31
 * before reaching a consistent point. This can happen because the slots can
32
 * be synced at some random time and we may not reach the consistent point
33
 * at the same WAL location as the primary. So, we mark such slots as
34
 * RS_TEMPORARY. Once the decoding from corresponding LSNs can reach a
35
 * consistent point, they will be marked as RS_PERSISTENT.
36
 *
37
 * If the WAL prior to the remote slot's confirmed_flush_lsn has not been
38
 * flushed on the standby, the slot is marked as RS_TEMPORARY. Once the standby
39
 * catches up and flushes that WAL, the slot will be marked as RS_PERSISTENT.
40
 *
41
 * The slot sync worker waits for some time before the next synchronization,
42
 * with the duration varying based on whether any slots were updated during
43
 * the last cycle. Refer to the comments above wait_for_slot_activity() for
44
 * more details.
45
 *
46
 * If the SQL function pg_sync_replication_slots() is used to sync the slots,
47
 * and if the slots are not ready to be synced and are marked as RS_TEMPORARY
48
 * because of any of the reasons mentioned above, then the SQL function also
49
 * waits and retries until the slots are marked as RS_PERSISTENT (which means
50
 * sync-ready). Refer to the comments in SyncReplicationSlots() for more
51
 * details.
52
 *
53
 * Any standby synchronized slots will be dropped if they no longer need
54
 * to be synchronized. See comment atop drop_local_obsolete_slots() for more
55
 * details.
56
 *---------------------------------------------------------------------------
57
 */
58
59
#include "postgres.h"
60
61
#include <time.h>
62
63
#include "access/xlog_internal.h"
64
#include "access/xlogrecovery.h"
65
#include "catalog/pg_database.h"
66
#include "libpq/pqsignal.h"
67
#include "pgstat.h"
68
#include "postmaster/interrupt.h"
69
#include "replication/logical.h"
70
#include "replication/logicalctl.h"
71
#include "replication/slotsync.h"
72
#include "replication/snapbuild.h"
73
#include "storage/ipc.h"
74
#include "storage/lmgr.h"
75
#include "storage/proc.h"
76
#include "storage/procarray.h"
77
#include "storage/subsystems.h"
78
#include "tcop/tcopprot.h"
79
#include "utils/builtins.h"
80
#include "utils/memutils.h"
81
#include "utils/pg_lsn.h"
82
#include "utils/ps_status.h"
83
#include "utils/timeout.h"
84
#include "utils/wait_event.h"
85
86
/*
87
 * Struct for sharing information to control slot synchronization.
88
 *
89
 * The 'pid' is either the slot sync worker's pid or the backend's pid running
90
 * the SQL function pg_sync_replication_slots(). On promotion, the startup
91
 * process sets 'stopSignaled' and uses this 'pid' to signal the synchronizing
92
 * process with PROCSIG_SLOTSYNC_MESSAGE and also to wake it up so that the
93
 * process can immediately stop its synchronizing work.
94
 * Setting 'stopSignaled' on the other hand is used to handle the race
95
 * condition when the postmaster has not noticed the promotion yet and thus may
96
 * end up restarting the slot sync worker. If 'stopSignaled' is set, the worker
97
 * will exit in such a case. The SQL function pg_sync_replication_slots() will
98
 * also error out if this flag is set. Note that we don't need to reset this
99
 * variable as after promotion the slot sync worker won't be restarted because
100
 * the pmState changes to PM_RUN from PM_HOT_STANDBY and we don't support
101
 * demoting primary without restarting the server.
102
 * See LaunchMissingBackgroundProcesses.
103
 *
104
 * The 'syncing' flag is needed to prevent concurrent slot syncs to avoid slot
105
 * overwrites.
106
 *
107
 * The 'last_start_time' is needed by postmaster to start the slot sync worker
108
 * once per SLOTSYNC_RESTART_INTERVAL_SEC. In cases where an immediate restart
109
 * is expected (e.g., slot sync GUCs change), slot sync worker will reset
110
 * last_start_time before exiting, so that postmaster can start the worker
111
 * without waiting for SLOTSYNC_RESTART_INTERVAL_SEC.
112
 */
113
typedef struct SlotSyncCtxStruct
114
{
115
  pid_t   pid;
116
  bool    stopSignaled;
117
  bool    syncing;
118
  time_t    last_start_time;
119
  slock_t   mutex;
120
} SlotSyncCtxStruct;
121
122
static SlotSyncCtxStruct *SlotSyncCtx = NULL;
123
124
static void SlotSyncShmemRequest(void *arg);
125
static void SlotSyncShmemInit(void *arg);
126
127
const ShmemCallbacks SlotSyncShmemCallbacks = {
128
  .request_fn = SlotSyncShmemRequest,
129
  .init_fn = SlotSyncShmemInit,
130
};
131
132
/* GUC variable */
133
bool    sync_replication_slots = false;
134
135
/*
136
 * The sleep time (ms) between slot-sync cycles varies dynamically
137
 * (within a MIN/MAX range) according to slot activity. See
138
 * wait_for_slot_activity() for details.
139
 */
140
0
#define MIN_SLOTSYNC_WORKER_NAPTIME_MS  200
141
#define MAX_SLOTSYNC_WORKER_NAPTIME_MS  30000 /* 30s */
142
143
static long sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
144
145
/* The restart interval for slot sync work used by postmaster */
146
0
#define SLOTSYNC_RESTART_INTERVAL_SEC 10
147
148
/*
149
 * Flag to tell if we are syncing replication slots. Unlike the 'syncing' flag
150
 * in SlotSyncCtxStruct, this flag is true only if the current process is
151
 * performing slot synchronization.
152
 */
153
static bool syncing_slots = false;
154
155
/*
156
 * Interrupt flag set when PROCSIG_SLOTSYNC_MESSAGE is received, asking the
157
 * slotsync worker or pg_sync_replication_slots() to stop because
158
 * standby promotion has been triggered.
159
 */
160
volatile sig_atomic_t SlotSyncShutdownPending = false;
161
162
/*
163
 * Structure to hold information fetched from the primary server about a logical
164
 * replication slot.
165
 */
166
typedef struct RemoteSlot
167
{
168
  char     *name;
169
  char     *plugin;
170
  char     *database;
171
  bool    two_phase;
172
  bool    failover;
173
  XLogRecPtr  restart_lsn;
174
  XLogRecPtr  confirmed_lsn;
175
  XLogRecPtr  two_phase_at;
176
  TransactionId catalog_xmin;
177
178
  /* RS_INVAL_NONE if valid, or the reason of invalidation */
179
  ReplicationSlotInvalidationCause invalidated;
180
} RemoteSlot;
181
182
static void slotsync_failure_callback(int code, Datum arg);
183
static void update_synced_slots_inactive_since(void);
184
185
/*
186
 * Update slot sync skip stats. This function requires the caller to acquire
187
 * the slot.
188
 */
189
static void
190
update_slotsync_skip_stats(SlotSyncSkipReason skip_reason)
191
0
{
192
0
  ReplicationSlot *slot;
193
194
0
  Assert(MyReplicationSlot);
195
196
0
  slot = MyReplicationSlot;
197
198
  /*
199
   * Update the slot sync related stats in pg_stat_replication_slots when a
200
   * slot sync is skipped
201
   */
202
0
  if (skip_reason != SS_SKIP_NONE)
203
0
    pgstat_report_replslotsync(slot);
204
205
  /* Update the slot sync skip reason */
206
0
  if (slot->slotsync_skip_reason != skip_reason)
207
0
  {
208
0
    SpinLockAcquire(&slot->mutex);
209
0
    slot->slotsync_skip_reason = skip_reason;
210
0
    SpinLockRelease(&slot->mutex);
211
0
  }
212
0
}
213
214
/*
215
 * If necessary, update the local synced slot's metadata based on the data
216
 * from the remote slot.
217
 *
218
 * If no update was needed (the data of the remote slot is the same as the
219
 * local slot) return false, otherwise true.
220
 */
221
static bool
222
update_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid)
223
0
{
224
0
  ReplicationSlot *slot = MyReplicationSlot;
225
0
  bool    updated_xmin_or_lsn = false;
226
0
  bool    updated_config = false;
227
0
  SlotSyncSkipReason skip_reason = SS_SKIP_NONE;
228
0
  XLogRecPtr  latestFlushPtr = GetStandbyFlushRecPtr(NULL);
229
230
0
  Assert(slot->data.invalidated == RS_INVAL_NONE);
231
232
  /*
233
   * Make sure that concerned WAL is received and flushed before syncing
234
   * slot to target lsn received from the primary server.
235
   */
236
0
  if (remote_slot->confirmed_lsn > latestFlushPtr)
237
0
  {
238
0
    update_slotsync_skip_stats(SS_SKIP_WAL_NOT_FLUSHED);
239
240
    /*
241
     * Can get here only if GUC 'synchronized_standby_slots' on the
242
     * primary server was not configured correctly.
243
     */
244
0
    ereport(LOG,
245
0
        errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
246
0
        errmsg("skipping slot synchronization because the received slot sync"
247
0
             " LSN %X/%08X for slot \"%s\" is ahead of the standby position %X/%08X",
248
0
             LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
249
0
             remote_slot->name,
250
0
             LSN_FORMAT_ARGS(latestFlushPtr)));
251
252
0
    return false;
253
0
  }
254
255
  /*
256
   * Don't overwrite if we already have a newer catalog_xmin and
257
   * restart_lsn.
258
   */
259
0
  if (remote_slot->restart_lsn < slot->data.restart_lsn ||
260
0
    TransactionIdPrecedes(remote_slot->catalog_xmin,
261
0
                slot->data.catalog_xmin))
262
0
  {
263
    /* Update slot sync skip stats */
264
0
    update_slotsync_skip_stats(SS_SKIP_WAL_OR_ROWS_REMOVED);
265
266
    /*
267
     * This can happen in following situations:
268
     *
269
     * If the slot is temporary, it means either the initial WAL location
270
     * reserved for the local slot is ahead of the remote slot's
271
     * restart_lsn or the initial xmin_horizon computed for the local slot
272
     * is ahead of the remote slot.
273
     *
274
     * If the slot is persistent, both restart_lsn and catalog_xmin of the
275
     * synced slot could still be ahead of the remote slot. Since we use
276
     * slot advance functionality to keep snapbuild/slot updated, it is
277
     * possible that the restart_lsn and catalog_xmin are advanced to a
278
     * later position than it has on the primary. This can happen when
279
     * slot advancing machinery finds running xacts record after reaching
280
     * the consistent state at a later point than the primary where it
281
     * serializes the snapshot and updates the restart_lsn.
282
     *
283
     * We LOG the message if the slot is temporary as it can help the user
284
     * to understand why the slot is not sync-ready. In the case of a
285
     * persistent slot, it would be a more common case and won't directly
286
     * impact the users, so we used DEBUG1 level to log the message.
287
     */
288
0
    ereport(slot->data.persistency == RS_TEMPORARY ? LOG : DEBUG1,
289
0
        errmsg("could not synchronize replication slot \"%s\"",
290
0
             remote_slot->name),
291
0
        errdetail("Synchronization could lead to data loss, because the remote slot needs WAL at LSN %X/%08X and catalog xmin %u, but the standby has LSN %X/%08X and catalog xmin %u.",
292
0
              LSN_FORMAT_ARGS(remote_slot->restart_lsn),
293
0
              remote_slot->catalog_xmin,
294
0
              LSN_FORMAT_ARGS(slot->data.restart_lsn),
295
0
              slot->data.catalog_xmin));
296
297
    /*
298
     * Skip updating the configuration. This is required to avoid syncing
299
     * two_phase_at without syncing confirmed_lsn. Otherwise, the prepared
300
     * transaction between old confirmed_lsn and two_phase_at will
301
     * unexpectedly get decoded and sent to the downstream after
302
     * promotion. See comments in ReorderBufferFinishPrepared.
303
     */
304
0
    return false;
305
0
  }
306
307
  /*
308
   * Attempt to sync LSNs and xmins only if remote slot is ahead of local
309
   * slot.
310
   */
311
0
  if (remote_slot->confirmed_lsn > slot->data.confirmed_flush ||
312
0
    remote_slot->restart_lsn > slot->data.restart_lsn ||
313
0
    TransactionIdFollows(remote_slot->catalog_xmin,
314
0
               slot->data.catalog_xmin))
315
0
  {
316
    /*
317
     * We can't directly copy the remote slot's LSN or xmin unless there
318
     * exists a consistent snapshot at that point. Otherwise, after
319
     * promotion, the slots may not reach a consistent point before the
320
     * confirmed_flush_lsn which can lead to a data loss. To avoid data
321
     * loss, we let slot machinery advance the slot which ensures that
322
     * snapbuilder/slot statuses are updated properly.
323
     */
324
0
    if (SnapBuildSnapshotExists(remote_slot->restart_lsn))
325
0
    {
326
      /*
327
       * Update the slot info directly if there is a serialized snapshot
328
       * at the restart_lsn, as the slot can quickly reach consistency
329
       * at restart_lsn by restoring the snapshot.
330
       */
331
0
      SpinLockAcquire(&slot->mutex);
332
0
      slot->data.restart_lsn = remote_slot->restart_lsn;
333
0
      slot->data.confirmed_flush = remote_slot->confirmed_lsn;
334
0
      slot->data.catalog_xmin = remote_slot->catalog_xmin;
335
0
      SpinLockRelease(&slot->mutex);
336
337
0
      updated_xmin_or_lsn = true;
338
0
    }
339
0
    else
340
0
    {
341
0
      bool    found_consistent_snapshot;
342
0
      XLogRecPtr  old_confirmed_lsn = slot->data.confirmed_flush;
343
0
      XLogRecPtr  old_restart_lsn = slot->data.restart_lsn;
344
0
      TransactionId old_catalog_xmin = slot->data.catalog_xmin;
345
346
0
      LogicalSlotAdvanceAndCheckSnapState(remote_slot->confirmed_lsn,
347
0
                        &found_consistent_snapshot);
348
349
      /* Sanity check */
350
0
      if (slot->data.confirmed_flush != remote_slot->confirmed_lsn)
351
0
        ereport(ERROR,
352
0
            errmsg_internal("synchronized confirmed_flush for slot \"%s\" differs from remote slot",
353
0
                    remote_slot->name),
354
0
            errdetail_internal("Remote slot has LSN %X/%08X but local slot has LSN %X/%08X.",
355
0
                       LSN_FORMAT_ARGS(remote_slot->confirmed_lsn),
356
0
                       LSN_FORMAT_ARGS(slot->data.confirmed_flush)));
357
358
      /*
359
       * If we can't reach a consistent snapshot, the slot won't be
360
       * persisted. See update_and_persist_local_synced_slot().
361
       */
362
0
      if (!found_consistent_snapshot)
363
0
      {
364
0
        Assert(MyReplicationSlot->data.persistency == RS_TEMPORARY);
365
366
0
        ereport(LOG,
367
0
            errmsg("could not synchronize replication slot \"%s\"",
368
0
                 remote_slot->name),
369
0
            errdetail("Synchronization could lead to data loss, because the standby could not build a consistent snapshot to decode WALs at LSN %X/%08X.",
370
0
                  LSN_FORMAT_ARGS(slot->data.restart_lsn)));
371
372
0
        skip_reason = SS_SKIP_NO_CONSISTENT_SNAPSHOT;
373
0
      }
374
375
      /*
376
       * It is possible that the slot's xmin or LSNs are not updated,
377
       * when the synced slot has reached consistent snapshot state or
378
       * cannot build one at all.
379
       */
380
0
      updated_xmin_or_lsn = (old_confirmed_lsn != slot->data.confirmed_flush ||
381
0
                   old_restart_lsn != slot->data.restart_lsn ||
382
0
                   old_catalog_xmin != slot->data.catalog_xmin);
383
0
    }
384
0
  }
385
386
  /* Update slot sync skip stats */
387
0
  update_slotsync_skip_stats(skip_reason);
388
389
0
  if (remote_dbid != slot->data.database ||
390
0
    remote_slot->two_phase != slot->data.two_phase ||
391
0
    remote_slot->failover != slot->data.failover ||
392
0
    strcmp(remote_slot->plugin, NameStr(slot->data.plugin)) != 0 ||
393
0
    remote_slot->two_phase_at != slot->data.two_phase_at)
394
0
  {
395
0
    NameData  plugin_name;
396
397
    /* Avoid expensive operations while holding a spinlock. */
398
0
    namestrcpy(&plugin_name, remote_slot->plugin);
399
400
0
    SpinLockAcquire(&slot->mutex);
401
0
    slot->data.plugin = plugin_name;
402
0
    slot->data.database = remote_dbid;
403
0
    slot->data.two_phase = remote_slot->two_phase;
404
0
    slot->data.two_phase_at = remote_slot->two_phase_at;
405
0
    slot->data.failover = remote_slot->failover;
406
0
    SpinLockRelease(&slot->mutex);
407
408
0
    updated_config = true;
409
410
    /*
411
     * Ensure that there is no risk of sending prepared transactions
412
     * unexpectedly after the promotion.
413
     */
414
0
    Assert(slot->data.two_phase_at <= slot->data.confirmed_flush);
415
0
  }
416
417
  /*
418
   * We have to write the changed xmin to disk *before* we change the
419
   * in-memory value, otherwise after a crash we wouldn't know that some
420
   * catalog tuples might have been removed already.
421
   */
422
0
  if (updated_config || updated_xmin_or_lsn)
423
0
  {
424
0
    ReplicationSlotMarkDirty();
425
0
    ReplicationSlotSave();
426
0
  }
427
428
  /*
429
   * Now the new xmin is safely on disk, we can let the global value
430
   * advance. We do not take ProcArrayLock or similar since we only advance
431
   * xmin here and there's not much harm done by a concurrent computation
432
   * missing that.
433
   */
434
0
  if (updated_xmin_or_lsn)
435
0
  {
436
0
    SpinLockAcquire(&slot->mutex);
437
0
    slot->effective_catalog_xmin = remote_slot->catalog_xmin;
438
0
    SpinLockRelease(&slot->mutex);
439
440
0
    ReplicationSlotsComputeRequiredXmin(false);
441
0
    ReplicationSlotsComputeRequiredLSN();
442
0
  }
443
444
0
  return updated_config || updated_xmin_or_lsn;
445
0
}
446
447
/*
448
 * Get the list of local logical slots that are synchronized from the
449
 * primary server.
450
 */
451
static List *
452
get_local_synced_slots(void)
453
0
{
454
0
  List     *local_slots = NIL;
455
456
0
  LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
457
458
0
  for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
459
0
  {
460
0
    ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
461
462
    /* Check if it is a synchronized slot */
463
0
    if (s->in_use && s->data.synced)
464
0
    {
465
0
      Assert(SlotIsLogical(s));
466
0
      local_slots = lappend(local_slots, s);
467
0
    }
468
0
  }
469
470
0
  LWLockRelease(ReplicationSlotControlLock);
471
472
0
  return local_slots;
473
0
}
474
475
/*
476
 * Helper function to check if local_slot is required to be retained.
477
 *
478
 * Return false either if local_slot does not exist in the remote_slots list
479
 * or is invalidated while the corresponding remote slot is still valid,
480
 * otherwise true.
481
 */
482
static bool
483
local_sync_slot_required(ReplicationSlot *local_slot, List *remote_slots)
484
0
{
485
0
  bool    remote_exists = false;
486
0
  bool    locally_invalidated = false;
487
488
0
  foreach_ptr(RemoteSlot, remote_slot, remote_slots)
489
0
  {
490
0
    if (strcmp(remote_slot->name, NameStr(local_slot->data.name)) == 0)
491
0
    {
492
0
      remote_exists = true;
493
494
      /*
495
       * If remote slot is not invalidated but local slot is marked as
496
       * invalidated, then set locally_invalidated flag.
497
       */
498
0
      SpinLockAcquire(&local_slot->mutex);
499
0
      locally_invalidated =
500
0
        (remote_slot->invalidated == RS_INVAL_NONE) &&
501
0
        (local_slot->data.invalidated != RS_INVAL_NONE);
502
0
      SpinLockRelease(&local_slot->mutex);
503
504
0
      break;
505
0
    }
506
0
  }
507
508
0
  return (remote_exists && !locally_invalidated);
509
0
}
510
511
/*
512
 * Drop local obsolete slots.
513
 *
514
 * Drop the local slots that no longer need to be synced i.e. these either do
515
 * not exist on the primary or are no longer enabled for failover.
516
 *
517
 * Additionally, drop any slots that are valid on the primary but got
518
 * invalidated on the standby. This situation may occur due to the following
519
 * reasons:
520
 * - The 'max_slot_wal_keep_size' on the standby is insufficient to retain WAL
521
 *   records from the restart_lsn of the slot.
522
 * - 'primary_slot_name' is temporarily reset to null and the physical slot is
523
 *   removed.
524
 * These dropped slots will get recreated in next sync-cycle and it is okay to
525
 * drop and recreate such slots as long as these are not consumable on the
526
 * standby (which is the case currently).
527
 *
528
 * Note: Change of 'wal_level' on the primary server to a level lower than
529
 * logical may also result in slot invalidation and removal on the standby.
530
 * This is because such 'wal_level' change is only possible if the logical
531
 * slots are removed on the primary server, so it's expected to see the
532
 * slots being invalidated and removed on the standby too (and re-created
533
 * if they are re-created on the primary server).
534
 */
535
static void
536
drop_local_obsolete_slots(List *remote_slot_list)
537
{
538
  List     *local_slots = get_local_synced_slots();
539
540
  foreach_ptr(ReplicationSlot, local_slot, local_slots)
541
  {
542
    /* Drop the local slot if it is not required to be retained. */
543
    if (!local_sync_slot_required(local_slot, remote_slot_list))
544
    {
545
      Oid     slot_database = local_slot->data.database;
546
      bool    synced_slot;
547
548
      /*
549
       * Use shared lock to prevent a conflict with
550
       * ReplicationSlotsDropDBSlots(), trying to drop the same slot
551
       * during a drop-database operation.
552
       */
553
      LockSharedObject(DatabaseRelationId, slot_database, 0,
554
               AccessShareLock);
555
556
      /*
557
       * In the small window between getting the slot to drop and
558
       * locking the database, there is a possibility of a parallel
559
       * database drop by the startup process and the creation of a new
560
       * slot by the user. This new user-created slot may end up using
561
       * the same shared memory as that of 'local_slot'.
562
       *
563
       * Because local_slot still points to a reusable slot-array entry,
564
       * its fields (name, database OID, invalidation state) may already
565
       * describe such a replacement slot by the time we reach here.
566
       * That means the drop decision made by local_sync_slot_required()
567
       * above could have been based on the replacement slot's data, and
568
       * slot_database could refer to an unrelated database. The recheck
569
       * below keeps us from actually dropping a user-created
570
       * replacement slot; the residual risk is confined to this cycle
571
       * (for example, briefly locking an unrelated database) and is
572
       * acceptable because the race is rare and non-fatal.
573
       */
574
      SpinLockAcquire(&local_slot->mutex);
575
      synced_slot = local_slot->in_use && local_slot->data.synced;
576
      SpinLockRelease(&local_slot->mutex);
577
578
      if (synced_slot)
579
      {
580
        NameData  slot_name = local_slot->data.name;
581
582
        /*
583
         * Now acquire and drop the slot.  Note we purposely don't
584
         * request logical decoding to be disabled here: since this is
585
         * a standby, which derives its logical decoding state from
586
         * the primary, it would be wrong to do so.
587
         */
588
        ReplicationSlotAcquire(NameStr(slot_name), true, false);
589
        ReplicationSlotDropAcquired(false);
590
591
        ereport(LOG,
592
            errmsg("dropped replication slot \"%s\" of database with OID %u",
593
                 NameStr(slot_name),
594
                 slot_database));
595
      }
596
597
      UnlockSharedObject(DatabaseRelationId, slot_database, 0,
598
                 AccessShareLock);
599
    }
600
  }
601
}
602
603
/*
604
 * Reserve WAL for the currently active local slot using the specified WAL
605
 * location (restart_lsn).
606
 *
607
 * If the given WAL location has been removed or is at risk of removal,
608
 * reserve WAL using the oldest segment that is non-removable.
609
 */
610
static void
611
reserve_wal_for_local_slot(XLogRecPtr restart_lsn)
612
0
{
613
0
  XLogRecPtr  slot_min_lsn;
614
0
  XLogRecPtr  min_safe_lsn;
615
0
  XLogSegNo segno;
616
0
  ReplicationSlot *slot = MyReplicationSlot;
617
618
0
  Assert(slot != NULL);
619
0
  Assert(!XLogRecPtrIsValid(slot->data.restart_lsn));
620
621
  /*
622
   * Acquire an exclusive lock to prevent the checkpoint process from
623
   * concurrently calculating the minimum slot LSN (see
624
   * CheckPointReplicationSlots), ensuring that if WAL reservation occurs
625
   * first, the checkpoint must wait for the restart_lsn update before
626
   * calculating the minimum LSN.
627
   *
628
   * Note: Unlike ReplicationSlotReserveWal(), this lock does not protect a
629
   * newly synced slot from being invalidated if a concurrent checkpoint has
630
   * invoked CheckPointReplicationSlots() before the WAL reservation here.
631
   * This can happen because the initial restart_lsn received from the
632
   * remote server can precede the redo pointer. Therefore, when selecting
633
   * the initial restart_lsn, we consider using the redo pointer or the
634
   * minimum slot LSN (if those values are greater than the remote
635
   * restart_lsn) instead of relying solely on the remote value.
636
   */
637
0
  LWLockAcquire(ReplicationSlotAllocationLock, LW_EXCLUSIVE);
638
639
  /*
640
   * Determine the minimum non-removable LSN by comparing the redo pointer
641
   * with the minimum slot LSN.
642
   *
643
   * The minimum slot LSN is considered because the redo pointer advances at
644
   * every checkpoint, even when replication slots are present on the
645
   * standby. In such scenarios, the redo pointer can exceed the remote
646
   * restart_lsn, while WALs preceding the remote restart_lsn remain
647
   * protected by a local replication slot.
648
   */
649
0
  min_safe_lsn = GetRedoRecPtr();
650
0
  slot_min_lsn = XLogGetReplicationSlotMinimumLSN();
651
652
0
  if (XLogRecPtrIsValid(slot_min_lsn) && min_safe_lsn > slot_min_lsn)
653
0
    min_safe_lsn = slot_min_lsn;
654
655
  /*
656
   * If the minimum safe LSN is greater than the given restart_lsn, use it
657
   * as the initial restart_lsn for the newly synced slot. Otherwise, use
658
   * the given remote restart_lsn.
659
   */
660
0
  SpinLockAcquire(&slot->mutex);
661
0
  slot->data.restart_lsn = Max(restart_lsn, min_safe_lsn);
662
0
  SpinLockRelease(&slot->mutex);
663
664
0
  ReplicationSlotsComputeRequiredLSN();
665
666
0
  XLByteToSeg(slot->data.restart_lsn, segno, wal_segment_size);
667
0
  if (XLogGetLastRemovedSegno() >= segno)
668
0
    elog(ERROR, "WAL required by replication slot %s has been removed concurrently",
669
0
       NameStr(slot->data.name));
670
671
0
  LWLockRelease(ReplicationSlotAllocationLock);
672
0
}
673
674
/*
675
 * If the remote restart_lsn and catalog_xmin have caught up with the
676
 * local ones, then update the LSNs and persist the local synced slot for
677
 * future synchronization; otherwise, do nothing.
678
 *
679
 * *slot_persistence_pending is set to true if any of the slots fail to
680
 * persist.
681
 *
682
 * Return true if the slot is marked as RS_PERSISTENT (sync-ready), otherwise
683
 * false.
684
 */
685
static bool
686
update_and_persist_local_synced_slot(RemoteSlot *remote_slot, Oid remote_dbid,
687
                   bool *slot_persistence_pending)
688
{
689
  ReplicationSlot *slot = MyReplicationSlot;
690
691
  /* Slotsync skip stats are handled in function update_local_synced_slot() */
692
  (void) update_local_synced_slot(remote_slot, remote_dbid);
693
694
  /*
695
   * Check if the slot cannot be synchronized. Refer to the comment atop the
696
   * file for details on this check.
697
   */
698
  if (slot->slotsync_skip_reason != SS_SKIP_NONE)
699
  {
700
    /*
701
     * We reach this point when the remote slot didn't catch up to locally
702
     * reserved position, or it cannot reach the consistent point from the
703
     * restart_lsn, or the WAL prior to the remote confirmed flush LSN has
704
     * not been received and flushed.
705
     *
706
     * We do not drop the slot because the restart_lsn and confirmed_lsn
707
     * can be ahead of the current location when recreating the slot in
708
     * the next cycle. It may take more time to create such a slot or
709
     * reach the consistent point. Therefore, we keep this slot and
710
     * attempt the synchronization in the next cycle.
711
     *
712
     * We also update the slot_persistence_pending parameter, so the SQL
713
     * function can retry.
714
     */
715
    if (slot_persistence_pending)
716
      *slot_persistence_pending = true;
717
718
    return false;
719
  }
720
721
  ReplicationSlotPersist();
722
723
  ereport(LOG,
724
      errmsg("newly created replication slot \"%s\" is sync-ready now",
725
           remote_slot->name));
726
727
  return true;
728
}
729
730
/*
731
 * Synchronize a single slot to the given position.
732
 *
733
 * This creates a new slot if there is no existing one and updates the
734
 * metadata of the slot as per the data received from the primary server.
735
 *
736
 * The slot is created as a temporary slot and stays in the same state until the
737
 * remote_slot catches up with locally reserved position and local slot is
738
 * updated. The slot is then persisted and is considered as sync-ready for
739
 * periodic syncs.
740
 *
741
 * *slot_persistence_pending is set to true if any of the slots fail to
742
 * persist.
743
 *
744
 * Returns TRUE if the local slot is updated.
745
 */
746
static bool
747
synchronize_one_slot(RemoteSlot *remote_slot, Oid remote_dbid,
748
           bool *slot_persistence_pending)
749
0
{
750
0
  ReplicationSlot *slot;
751
0
  bool    slot_updated = false;
752
753
  /* Search for the named slot */
754
0
  if ((slot = SearchNamedReplicationSlot(remote_slot->name, true)))
755
0
  {
756
0
    bool    synced;
757
758
0
    SpinLockAcquire(&slot->mutex);
759
0
    synced = slot->data.synced;
760
0
    SpinLockRelease(&slot->mutex);
761
762
    /* User-created slot with the same name exists, raise ERROR. */
763
0
    if (!synced)
764
0
      ereport(ERROR,
765
0
          errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
766
0
          errmsg("exiting from slot synchronization because same"
767
0
               " name slot \"%s\" already exists on the standby",
768
0
               remote_slot->name));
769
770
    /*
771
     * The slot has been synchronized before.
772
     *
773
     * It is important to acquire the slot here before checking
774
     * invalidation. If we don't acquire the slot first, there could be a
775
     * race condition that the local slot could be invalidated just after
776
     * checking the 'invalidated' flag here and we could end up
777
     * overwriting 'invalidated' flag to remote_slot's value. See
778
     * InvalidatePossiblyObsoleteSlot() where it invalidates slot directly
779
     * if the slot is not acquired by other processes.
780
     *
781
     * XXX: If it ever turns out that slot acquire/release is costly for
782
     * cases when none of the slot properties is changed then we can do a
783
     * pre-check to ensure that at least one of the slot properties is
784
     * changed before acquiring the slot.
785
     */
786
0
    ReplicationSlotAcquire(remote_slot->name, true, false);
787
788
0
    Assert(slot == MyReplicationSlot);
789
790
    /*
791
     * Copy the invalidation cause from remote only if local slot is not
792
     * invalidated locally, we don't want to overwrite existing one.
793
     */
794
0
    if (slot->data.invalidated == RS_INVAL_NONE &&
795
0
      remote_slot->invalidated != RS_INVAL_NONE)
796
0
    {
797
0
      SpinLockAcquire(&slot->mutex);
798
0
      slot->data.invalidated = remote_slot->invalidated;
799
0
      SpinLockRelease(&slot->mutex);
800
801
      /* Make sure the invalidated state persists across server restart */
802
0
      ReplicationSlotMarkDirty();
803
0
      ReplicationSlotSave();
804
805
0
      slot_updated = true;
806
0
    }
807
808
    /* Skip the sync of an invalidated slot */
809
0
    if (slot->data.invalidated != RS_INVAL_NONE)
810
0
    {
811
0
      update_slotsync_skip_stats(SS_SKIP_INVALID);
812
813
0
      ReplicationSlotRelease();
814
0
      return slot_updated;
815
0
    }
816
817
    /* Slot not ready yet, let's attempt to make it sync-ready now. */
818
0
    if (slot->data.persistency == RS_TEMPORARY)
819
0
    {
820
0
      slot_updated = update_and_persist_local_synced_slot(remote_slot,
821
0
                                remote_dbid,
822
0
                                slot_persistence_pending);
823
0
    }
824
825
    /* Slot ready for sync, so sync it. */
826
0
    else
827
0
    {
828
      /*
829
       * Sanity check: As long as the invalidations are handled
830
       * appropriately as above, this should never happen.
831
       *
832
       * We don't need to check restart_lsn here. See the comments in
833
       * update_local_synced_slot() for details.
834
       */
835
0
      if (remote_slot->confirmed_lsn < slot->data.confirmed_flush)
836
0
        ereport(ERROR,
837
0
            errmsg_internal("cannot synchronize local slot \"%s\"",
838
0
                    remote_slot->name),
839
0
            errdetail_internal("Local slot's start streaming location LSN(%X/%08X) is ahead of remote slot's LSN(%X/%08X).",
840
0
                       LSN_FORMAT_ARGS(slot->data.confirmed_flush),
841
0
                       LSN_FORMAT_ARGS(remote_slot->confirmed_lsn)));
842
843
0
      slot_updated = update_local_synced_slot(remote_slot, remote_dbid);
844
0
    }
845
0
  }
846
  /* Otherwise create the slot first. */
847
0
  else
848
0
  {
849
0
    NameData  plugin_name;
850
0
    TransactionId xmin_horizon = InvalidTransactionId;
851
0
    XLogRecPtr  replay_lsn;
852
853
    /* Skip creating the local slot if remote_slot is invalidated already */
854
0
    if (remote_slot->invalidated != RS_INVAL_NONE)
855
0
      return false;
856
857
    /*
858
     * We create temporary slots instead of ephemeral slots here because
859
     * we want the slots to survive after releasing them. This is done to
860
     * avoid dropping and re-creating the slots in each synchronization
861
     * cycle if the restart_lsn or catalog_xmin of the remote slot has not
862
     * caught up.
863
     */
864
0
    ReplicationSlotCreate(remote_slot->name, true, RS_TEMPORARY,
865
0
                remote_slot->two_phase,
866
0
                false,
867
0
                remote_slot->failover,
868
0
                true);
869
870
    /*
871
     * The remote slot information can predate a status change record that
872
     * this standby has already replayed. That happens when the last
873
     * logical slot on the primary is dropped, and possibly re-created
874
     * with the same name, after fetch_remote_slots() ran. The resulting
875
     * deactivation could not invalidate our slot because it did not exist
876
     * yet, and WAL following the (stale) remote restart_lsn may lack the
877
     * information logical decoding needs. Checking only whether logical
878
     * decoding is enabled is not enough, as it can have been disabled and
879
     * enabled again in the meantime.
880
     *
881
     * The check has to come after ReplicationSlotCreate(), which makes
882
     * the slot both visible and acquired. A deactivation replayed from
883
     * here on finds the slot in InvalidatePossiblyObsoleteSlot(), signals
884
     * a recovery conflict and waits for the slot to be released before
885
     * invalidating it (only in hot standby, which slot synchronization
886
     * requires anyway). Replay therefore cannot get past that record
887
     * behind our back, so the slot never needs to be rechecked before
888
     * being persisted.
889
     *
890
     * The check only runs once replay has reached the remote restart_lsn;
891
     * otherwise it is skipped and the slot is kept as-is. Without this, a
892
     * standby lagging behind the primary (replay paused, or a large
893
     * recovery_min_apply_delay) could fetch a live, valid restart_lsn
894
     * from the primary and have it rejected by
895
     * StandbyLogicalDecodingEnabledSince(), whose answer reflects only
896
     * WAL replayed so far and says nothing about an LSN replay hasn't
897
     * reached yet. That would drop a perfectly good slot every cycle.
898
     * Skipping the check is safe: only a deactivation at or after the
899
     * remote restart_lsn can leave the WAL that follows it without the
900
     * information logical decoding needs, and replay has not reached such
901
     * a record yet. Replaying it later invalidates the slot whether or
902
     * not it has been persisted by then.
903
     *
904
     * Even so, the comparison uses the remote restart_lsn rather than the
905
     * local one, so a slot that would have been usable may be dropped;
906
     * the next cycle fetches fresh information. The slot cannot be kept,
907
     * as it would go on using the stale restart_lsn.
908
     */
909
0
    replay_lsn = GetXLogReplayRecPtr(NULL);
910
0
    if (remote_slot->restart_lsn <= replay_lsn &&
911
0
      !StandbyLogicalDecodingEnabledSince(remote_slot->restart_lsn))
912
0
    {
913
0
      ereport(LOG,
914
0
          errmsg("could not synchronize replication slot \"%s\"",
915
0
               remote_slot->name),
916
0
          errdetail("Logical decoding was disabled after the remote slot's restart LSN %X/%08X.",
917
0
                LSN_FORMAT_ARGS(remote_slot->restart_lsn)));
918
919
0
      ReplicationSlotDropAcquired(false);
920
921
0
      if (slot_persistence_pending)
922
0
        *slot_persistence_pending = true;
923
924
0
      return false;
925
0
    }
926
927
    /* For shorter lines. */
928
0
    slot = MyReplicationSlot;
929
930
    /* Avoid expensive operations while holding a spinlock. */
931
0
    namestrcpy(&plugin_name, remote_slot->plugin);
932
933
0
    SpinLockAcquire(&slot->mutex);
934
0
    slot->data.database = remote_dbid;
935
0
    slot->data.plugin = plugin_name;
936
0
    SpinLockRelease(&slot->mutex);
937
938
0
    reserve_wal_for_local_slot(remote_slot->restart_lsn);
939
940
0
    LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);
941
0
    LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
942
0
    xmin_horizon = GetOldestSafeDecodingTransactionId(true);
943
0
    SpinLockAcquire(&slot->mutex);
944
0
    slot->effective_catalog_xmin = xmin_horizon;
945
0
    slot->data.catalog_xmin = xmin_horizon;
946
0
    SpinLockRelease(&slot->mutex);
947
0
    ReplicationSlotsComputeRequiredXmin(true);
948
0
    LWLockRelease(ProcArrayLock);
949
0
    LWLockRelease(ReplicationSlotControlLock);
950
951
0
    update_and_persist_local_synced_slot(remote_slot, remote_dbid,
952
0
                       slot_persistence_pending);
953
954
0
    slot_updated = true;
955
0
  }
956
957
0
  ReplicationSlotRelease();
958
959
0
  return slot_updated;
960
0
}
961
962
/*
963
 * Fetch remote slots.
964
 *
965
 * If slot_names is NIL, fetches all failover logical slots from the
966
 * primary server, otherwise fetches only the ones with names in slot_names.
967
 *
968
 * Returns a list of remote slot information structures, or NIL if none
969
 * are found.
970
 */
971
static List *
972
fetch_remote_slots(WalReceiverConn *wrconn, List *slot_names)
973
0
{
974
0
#define SLOTSYNC_COLUMN_COUNT 10
975
0
  Oid     slotRow[SLOTSYNC_COLUMN_COUNT] = {TEXTOID, TEXTOID, LSNOID,
976
0
  LSNOID, XIDOID, BOOLOID, LSNOID, BOOLOID, TEXTOID, TEXTOID};
977
978
0
  WalRcvExecResult *res;
979
0
  TupleTableSlot *tupslot;
980
0
  List     *remote_slot_list = NIL;
981
0
  StringInfoData query;
982
983
0
  initStringInfo(&query);
984
0
  appendStringInfoString(&query,
985
0
               "SELECT slot_name, plugin, confirmed_flush_lsn,"
986
0
               " restart_lsn, catalog_xmin, two_phase,"
987
0
               " two_phase_at, failover,"
988
0
               " database, invalidation_reason"
989
0
               " FROM pg_catalog.pg_replication_slots"
990
0
               " WHERE failover and NOT temporary");
991
992
0
  if (slot_names != NIL)
993
0
  {
994
0
    bool    first_slot = true;
995
996
    /*
997
     * Construct the query to fetch only the specified slots
998
     */
999
0
    appendStringInfoString(&query, " AND slot_name IN (");
1000
1001
0
    foreach_ptr(char, slot_name, slot_names)
1002
0
    {
1003
0
      if (!first_slot)
1004
0
        appendStringInfoString(&query, ", ");
1005
1006
0
      appendStringInfoString(&query, quote_literal_cstr(slot_name));
1007
0
      first_slot = false;
1008
0
    }
1009
0
    appendStringInfoChar(&query, ')');
1010
0
  }
1011
1012
  /* Execute the query */
1013
0
  res = walrcv_exec(wrconn, query.data, SLOTSYNC_COLUMN_COUNT, slotRow);
1014
0
  pfree(query.data);
1015
0
  if (res->status != WALRCV_OK_TUPLES)
1016
0
    ereport(ERROR,
1017
0
        errmsg("could not fetch failover logical slots info from the primary server: %s",
1018
0
             res->err));
1019
1020
0
  tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
1021
0
  while (tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
1022
0
  {
1023
0
    bool    isnull;
1024
0
    RemoteSlot *remote_slot = palloc0_object(RemoteSlot);
1025
0
    Datum   d;
1026
0
    int     col = 0;
1027
1028
0
    remote_slot->name = TextDatumGetCString(slot_getattr(tupslot, ++col,
1029
0
                               &isnull));
1030
0
    Assert(!isnull);
1031
1032
0
    remote_slot->plugin = TextDatumGetCString(slot_getattr(tupslot, ++col,
1033
0
                                 &isnull));
1034
0
    Assert(!isnull);
1035
1036
    /*
1037
     * It is possible to get null values for LSN and Xmin if slot is
1038
     * invalidated on the primary server, so handle accordingly.
1039
     */
1040
0
    d = slot_getattr(tupslot, ++col, &isnull);
1041
0
    remote_slot->confirmed_lsn = isnull ? InvalidXLogRecPtr :
1042
0
      DatumGetLSN(d);
1043
1044
0
    d = slot_getattr(tupslot, ++col, &isnull);
1045
0
    remote_slot->restart_lsn = isnull ? InvalidXLogRecPtr : DatumGetLSN(d);
1046
1047
0
    d = slot_getattr(tupslot, ++col, &isnull);
1048
0
    remote_slot->catalog_xmin = isnull ? InvalidTransactionId :
1049
0
      DatumGetTransactionId(d);
1050
1051
0
    remote_slot->two_phase = DatumGetBool(slot_getattr(tupslot, ++col,
1052
0
                               &isnull));
1053
0
    Assert(!isnull);
1054
1055
0
    d = slot_getattr(tupslot, ++col, &isnull);
1056
0
    remote_slot->two_phase_at = isnull ? InvalidXLogRecPtr : DatumGetLSN(d);
1057
1058
0
    remote_slot->failover = DatumGetBool(slot_getattr(tupslot, ++col,
1059
0
                              &isnull));
1060
0
    Assert(!isnull);
1061
1062
0
    remote_slot->database = TextDatumGetCString(slot_getattr(tupslot,
1063
0
                                 ++col, &isnull));
1064
0
    Assert(!isnull);
1065
1066
0
    d = slot_getattr(tupslot, ++col, &isnull);
1067
0
    remote_slot->invalidated = isnull ? RS_INVAL_NONE :
1068
0
      GetSlotInvalidationCause(TextDatumGetCString(d));
1069
1070
    /* Sanity check */
1071
0
    Assert(col == SLOTSYNC_COLUMN_COUNT);
1072
1073
    /*
1074
     * If restart_lsn, confirmed_lsn or catalog_xmin is invalid but the
1075
     * slot is valid, that means we have fetched the remote_slot in its
1076
     * RS_EPHEMERAL state. In such a case, don't sync it; we can always
1077
     * sync it in the next sync cycle when the remote_slot is persisted
1078
     * and has valid lsn(s) and xmin values.
1079
     *
1080
     * XXX: In future, if we plan to expose 'slot->data.persistency' in
1081
     * pg_replication_slots view, then we can avoid fetching RS_EPHEMERAL
1082
     * slots in the first place.
1083
     */
1084
0
    if ((!XLogRecPtrIsValid(remote_slot->restart_lsn) ||
1085
0
       !XLogRecPtrIsValid(remote_slot->confirmed_lsn) ||
1086
0
       !TransactionIdIsValid(remote_slot->catalog_xmin)) &&
1087
0
      remote_slot->invalidated == RS_INVAL_NONE)
1088
0
      pfree(remote_slot);
1089
0
    else
1090
      /* Create list of remote slots */
1091
0
      remote_slot_list = lappend(remote_slot_list, remote_slot);
1092
1093
0
    ExecClearTuple(tupslot);
1094
0
  }
1095
1096
0
  ExecDropSingleTupleTableSlot(tupslot);
1097
0
  walrcv_clear_result(res);
1098
1099
0
  return remote_slot_list;
1100
0
}
1101
1102
/*
1103
 * Synchronize slots.
1104
 *
1105
 * This function takes a list of remote slots and synchronizes them locally. It
1106
 * creates the slots if not present on the standby and updates existing ones.
1107
 *
1108
 * If slot_persistence_pending is not NULL, it will be set to true if one or
1109
 * more slots could not be persisted. This allows callers such as
1110
 * SyncReplicationSlots() to retry those slots.
1111
 *
1112
 * Returns TRUE if any of the slots gets updated in this sync-cycle.
1113
 */
1114
static bool
1115
synchronize_slots(WalReceiverConn *wrconn, List *remote_slot_list,
1116
          bool *slot_persistence_pending)
1117
0
{
1118
0
  bool    some_slot_updated = false;
1119
1120
  /* Drop local slots that no longer need to be synced. */
1121
0
  drop_local_obsolete_slots(remote_slot_list);
1122
1123
  /* Now sync the slots locally */
1124
0
  foreach_ptr(RemoteSlot, remote_slot, remote_slot_list)
1125
0
  {
1126
0
    Oid     remote_dbid = get_database_oid(remote_slot->database, false);
1127
1128
    /*
1129
     * Use shared lock to prevent a conflict with
1130
     * ReplicationSlotsDropDBSlots(), trying to drop the same slot during
1131
     * a drop-database operation.
1132
     */
1133
0
    LockSharedObject(DatabaseRelationId, remote_dbid, 0, AccessShareLock);
1134
1135
0
    some_slot_updated |= synchronize_one_slot(remote_slot, remote_dbid,
1136
0
                          slot_persistence_pending);
1137
1138
0
    UnlockSharedObject(DatabaseRelationId, remote_dbid, 0, AccessShareLock);
1139
0
  }
1140
1141
0
  return some_slot_updated;
1142
0
}
1143
1144
/*
1145
 * Checks the remote server info.
1146
 *
1147
 * We ensure that the 'primary_slot_name' exists on the remote server and the
1148
 * remote server is not a standby node.
1149
 */
1150
static void
1151
validate_remote_info(WalReceiverConn *wrconn)
1152
0
{
1153
0
#define PRIMARY_INFO_OUTPUT_COL_COUNT 2
1154
0
  WalRcvExecResult *res;
1155
0
  Oid     slotRow[PRIMARY_INFO_OUTPUT_COL_COUNT] = {BOOLOID, BOOLOID};
1156
0
  StringInfoData cmd;
1157
0
  bool    isnull;
1158
0
  TupleTableSlot *tupslot;
1159
0
  bool    remote_in_recovery;
1160
0
  bool    primary_slot_valid;
1161
0
  bool    started_tx = false;
1162
1163
0
  initStringInfo(&cmd);
1164
0
  appendStringInfo(&cmd,
1165
0
           "SELECT pg_is_in_recovery(), count(*) = 1"
1166
0
           " FROM pg_catalog.pg_replication_slots"
1167
0
           " WHERE slot_type='physical' AND slot_name=%s",
1168
0
           quote_literal_cstr(PrimarySlotName));
1169
1170
  /* The syscache access in walrcv_exec() needs a transaction env. */
1171
0
  if (!IsTransactionState())
1172
0
  {
1173
0
    StartTransactionCommand();
1174
0
    started_tx = true;
1175
0
  }
1176
1177
0
  res = walrcv_exec(wrconn, cmd.data, PRIMARY_INFO_OUTPUT_COL_COUNT, slotRow);
1178
0
  pfree(cmd.data);
1179
1180
0
  if (res->status != WALRCV_OK_TUPLES)
1181
0
    ereport(ERROR,
1182
0
        errmsg("could not fetch primary slot name \"%s\" info from the primary server: %s",
1183
0
             PrimarySlotName, res->err),
1184
0
        errhint("Check if \"primary_slot_name\" is configured correctly."));
1185
1186
0
  tupslot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
1187
0
  if (!tuplestore_gettupleslot(res->tuplestore, true, false, tupslot))
1188
0
    elog(ERROR,
1189
0
       "failed to fetch tuple for the primary server slot specified by \"primary_slot_name\"");
1190
1191
0
  remote_in_recovery = DatumGetBool(slot_getattr(tupslot, 1, &isnull));
1192
0
  Assert(!isnull);
1193
1194
  /*
1195
   * Slot sync is currently not supported on a cascading standby. This is
1196
   * because if we allow it, the primary server needs to wait for all the
1197
   * cascading standbys, otherwise, logical subscribers can still be ahead
1198
   * of one of the cascading standbys which we plan to promote. Thus, to
1199
   * avoid this additional complexity, we restrict it for the time being.
1200
   */
1201
0
  if (remote_in_recovery)
1202
0
    ereport(ERROR,
1203
0
        errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1204
0
        errmsg("cannot synchronize replication slots from a standby server"));
1205
1206
0
  primary_slot_valid = DatumGetBool(slot_getattr(tupslot, 2, &isnull));
1207
0
  Assert(!isnull);
1208
1209
0
  if (!primary_slot_valid)
1210
0
    ereport(ERROR,
1211
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1212
    /* translator: second %s is a GUC variable name */
1213
0
        errmsg("replication slot \"%s\" specified by \"%s\" does not exist on primary server",
1214
0
             PrimarySlotName, "primary_slot_name"));
1215
1216
0
  ExecDropSingleTupleTableSlot(tupslot);
1217
0
  walrcv_clear_result(res);
1218
1219
0
  if (started_tx)
1220
0
    CommitTransactionCommand();
1221
0
}
1222
1223
/*
1224
 * Checks if dbname is specified in 'primary_conninfo'.
1225
 *
1226
 * Error out if not specified otherwise return it.
1227
 */
1228
char *
1229
CheckAndGetDbnameFromConninfo(void)
1230
0
{
1231
0
  char     *dbname;
1232
1233
  /*
1234
   * The slot synchronization needs a database connection for walrcv_exec to
1235
   * work.
1236
   */
1237
0
  dbname = walrcv_get_dbname_from_conninfo(PrimaryConnInfo);
1238
0
  if (dbname == NULL)
1239
0
    ereport(ERROR,
1240
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1241
1242
    /*
1243
     * translator: first %s is a connection option; second %s is a GUC
1244
     * variable name
1245
     */
1246
0
        errmsg("replication slot synchronization requires \"%s\" to be specified in \"%s\"",
1247
0
             "dbname", "primary_conninfo"));
1248
0
  return dbname;
1249
0
}
1250
1251
/*
1252
 * Return true if all necessary GUCs for slot synchronization are set
1253
 * appropriately, otherwise, return false.
1254
 */
1255
bool
1256
ValidateSlotSyncParams(int elevel)
1257
0
{
1258
  /*
1259
   * Logical slot sync/creation requires logical decoding to be enabled.
1260
   */
1261
0
  if (!IsLogicalDecodingEnabled())
1262
0
  {
1263
0
    ereport(elevel,
1264
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1265
0
        errmsg("replication slot synchronization requires \"effective_wal_level\" >= \"logical\" on the primary"),
1266
0
        errhint("To enable logical decoding on primary, set \"wal_level\" >= \"logical\" or create at least one logical slot when \"wal_level\" = \"replica\"."));
1267
1268
0
    return false;
1269
0
  }
1270
1271
  /*
1272
   * A physical replication slot(primary_slot_name) is required on the
1273
   * primary to ensure that the rows needed by the standby are not removed
1274
   * after restarting, so that the synchronized slot on the standby will not
1275
   * be invalidated.
1276
   */
1277
0
  if (PrimarySlotName == NULL || *PrimarySlotName == '\0')
1278
0
  {
1279
0
    ereport(elevel,
1280
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1281
    /* translator: %s is a GUC variable name */
1282
0
        errmsg("replication slot synchronization requires \"%s\" to be set", "primary_slot_name"));
1283
0
    return false;
1284
0
  }
1285
1286
  /*
1287
   * hot_standby_feedback must be enabled to cooperate with the physical
1288
   * replication slot, which allows informing the primary about the xmin and
1289
   * catalog_xmin values on the standby.
1290
   */
1291
0
  if (!hot_standby_feedback)
1292
0
  {
1293
0
    ereport(elevel,
1294
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1295
    /* translator: %s is a GUC variable name */
1296
0
        errmsg("replication slot synchronization requires \"%s\" to be enabled",
1297
0
             "hot_standby_feedback"));
1298
0
    return false;
1299
0
  }
1300
1301
  /*
1302
   * The primary_conninfo is required to make connection to primary for
1303
   * getting slots information.
1304
   */
1305
0
  if (PrimaryConnInfo == NULL || *PrimaryConnInfo == '\0')
1306
0
  {
1307
0
    ereport(elevel,
1308
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1309
    /* translator: %s is a GUC variable name */
1310
0
        errmsg("replication slot synchronization requires \"%s\" to be set",
1311
0
             "primary_conninfo"));
1312
0
    return false;
1313
0
  }
1314
1315
0
  return true;
1316
0
}
1317
1318
/*
1319
 * Re-read the config file for slot synchronization.
1320
 *
1321
 * Exit or throw error if relevant GUCs have changed depending on whether
1322
 * called from slot sync worker or from the SQL function pg_sync_replication_slots()
1323
 */
1324
static void
1325
slotsync_reread_config(void)
1326
0
{
1327
0
  char     *old_primary_conninfo = pstrdup(PrimaryConnInfo);
1328
0
  char     *old_primary_slotname = pstrdup(PrimarySlotName);
1329
0
  bool    old_sync_replication_slots = sync_replication_slots;
1330
0
  bool    old_hot_standby_feedback = hot_standby_feedback;
1331
0
  bool    conninfo_changed;
1332
0
  bool    primary_slotname_changed;
1333
0
  bool    is_slotsync_worker = AmLogicalSlotSyncWorkerProcess();
1334
0
  bool    parameter_changed = false;
1335
1336
0
  if (is_slotsync_worker)
1337
0
    Assert(sync_replication_slots);
1338
1339
0
  ConfigReloadPending = false;
1340
0
  ProcessConfigFile(PGC_SIGHUP);
1341
1342
0
  conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
1343
0
  primary_slotname_changed = strcmp(old_primary_slotname, PrimarySlotName) != 0;
1344
0
  pfree(old_primary_conninfo);
1345
0
  pfree(old_primary_slotname);
1346
1347
0
  if (old_sync_replication_slots != sync_replication_slots)
1348
0
  {
1349
0
    if (is_slotsync_worker)
1350
0
    {
1351
0
      ereport(LOG,
1352
      /* translator: %s is a GUC variable name */
1353
0
          errmsg("replication slot synchronization worker will stop because \"%s\" is disabled",
1354
0
               "sync_replication_slots"));
1355
1356
0
      proc_exit(0);
1357
0
    }
1358
1359
0
    parameter_changed = true;
1360
0
  }
1361
0
  else
1362
0
  {
1363
0
    if (conninfo_changed ||
1364
0
      primary_slotname_changed ||
1365
0
      (old_hot_standby_feedback != hot_standby_feedback))
1366
0
    {
1367
1368
0
      if (is_slotsync_worker)
1369
0
      {
1370
0
        ereport(LOG,
1371
0
            errmsg("replication slot synchronization worker will restart because of a parameter change"));
1372
1373
        /*
1374
         * Reset the last-start time for this worker so that the
1375
         * postmaster can restart it without waiting for
1376
         * SLOTSYNC_RESTART_INTERVAL_SEC.
1377
         */
1378
0
        SlotSyncCtx->last_start_time = 0;
1379
1380
0
        proc_exit(0);
1381
0
      }
1382
1383
0
      parameter_changed = true;
1384
0
    }
1385
0
  }
1386
1387
  /*
1388
   * If we have reached here with a parameter change, we must be running in
1389
   * SQL function, emit error in such a case.
1390
   */
1391
0
  if (parameter_changed)
1392
0
  {
1393
0
    Assert(!is_slotsync_worker);
1394
0
    ereport(ERROR,
1395
0
        errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1396
0
        errmsg("replication slot synchronization will stop because of a parameter change"));
1397
0
  }
1398
1399
0
}
1400
1401
/*
1402
 * Handle receipt of an interrupt indicating a slotsync shutdown message.
1403
 *
1404
 * This is called within the SIGUSR1 handler.  All we do here is set a flag
1405
 * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
1406
 * ProcessSlotSyncMessage().
1407
 */
1408
void
1409
HandleSlotSyncMessageInterrupt(void)
1410
0
{
1411
0
  InterruptPending = true;
1412
0
  SlotSyncShutdownPending = true;
1413
  /* latch will be set by procsignal_sigusr1_handler */
1414
0
}
1415
1416
/*
1417
 * Handle a PROCSIG_SLOTSYNC_MESSAGE signal, called from ProcessInterrupts().
1418
 *
1419
 * If the current process is the slotsync background worker, log a message
1420
 * and exit cleanly.  If it is a backend executing pg_sync_replication_slots(),
1421
 * raise an error, unless the sync has already finished, in which case there
1422
 * is no need to interrupt the caller.
1423
 */
1424
void
1425
ProcessSlotSyncMessage(void)
1426
0
{
1427
0
  SlotSyncShutdownPending = false;
1428
1429
0
  if (AmLogicalSlotSyncWorkerProcess())
1430
0
  {
1431
0
    ereport(LOG,
1432
0
        errmsg("replication slot synchronization worker will stop because promotion is triggered"));
1433
0
    proc_exit(0);
1434
0
  }
1435
0
  else
1436
0
  {
1437
    /*
1438
     * If sync has already completed, there is no need to interrupt the
1439
     * caller with an error.
1440
     */
1441
0
    if (!IsSyncingReplicationSlots())
1442
0
      return;
1443
1444
0
    ereport(ERROR,
1445
0
        errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1446
0
        errmsg("replication slot synchronization will stop because promotion is triggered"));
1447
0
  }
1448
0
}
1449
1450
/*
1451
 * Connection cleanup function for slotsync worker.
1452
 *
1453
 * Called on slotsync worker exit.
1454
 */
1455
static void
1456
slotsync_worker_disconnect(int code, Datum arg)
1457
0
{
1458
0
  WalReceiverConn *wrconn = (WalReceiverConn *) DatumGetPointer(arg);
1459
1460
0
  walrcv_disconnect(wrconn);
1461
0
}
1462
1463
/*
1464
 * Cleanup function for slotsync worker.
1465
 *
1466
 * Called on slotsync worker exit.
1467
 */
1468
static void
1469
slotsync_worker_onexit(int code, Datum arg)
1470
0
{
1471
  /*
1472
   * We need to do slots cleanup here just like WalSndErrorCleanup() does.
1473
   *
1474
   * The startup process during promotion invokes ShutDownSlotSync() which
1475
   * waits for slot sync to finish and it does that by checking the
1476
   * 'syncing' flag. Thus the slot sync worker must be done with slots'
1477
   * release and cleanup to avoid any dangling temporary slots or active
1478
   * slots before it marks itself as finished syncing.
1479
   */
1480
1481
  /* Make sure active replication slots are released */
1482
0
  if (MyReplicationSlot != NULL)
1483
0
    ReplicationSlotRelease();
1484
1485
  /* Also cleanup the temporary slots. */
1486
0
  ReplicationSlotCleanup(false);
1487
1488
0
  SpinLockAcquire(&SlotSyncCtx->mutex);
1489
1490
0
  SlotSyncCtx->pid = InvalidPid;
1491
1492
  /*
1493
   * If syncing_slots is true, it indicates that the process errored out
1494
   * without resetting the flag. So, we need to clean up shared memory and
1495
   * reset the flag here.
1496
   */
1497
0
  if (syncing_slots)
1498
0
  {
1499
0
    SlotSyncCtx->syncing = false;
1500
0
    syncing_slots = false;
1501
0
  }
1502
1503
0
  SpinLockRelease(&SlotSyncCtx->mutex);
1504
0
}
1505
1506
/*
1507
 * Sleep for long enough that we believe it's likely that the slots on primary
1508
 * get updated.
1509
 *
1510
 * If there is no slot activity the wait time between sync-cycles will double
1511
 * (to a maximum of 30s). If there is some slot activity the wait time between
1512
 * sync-cycles is reset to the minimum (200ms).
1513
 */
1514
static void
1515
wait_for_slot_activity(bool some_slot_updated)
1516
0
{
1517
0
  int     rc;
1518
1519
0
  if (!some_slot_updated)
1520
0
  {
1521
    /*
1522
     * No slots were updated, so double the sleep time, but not beyond the
1523
     * maximum allowable value.
1524
     */
1525
0
    sleep_ms = Min(sleep_ms * 2, MAX_SLOTSYNC_WORKER_NAPTIME_MS);
1526
0
  }
1527
0
  else
1528
0
  {
1529
    /*
1530
     * Some slots were updated since the last sleep, so reset the sleep
1531
     * time.
1532
     */
1533
0
    sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
1534
0
  }
1535
1536
0
  rc = WaitLatch(MyLatch,
1537
0
           WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
1538
0
           sleep_ms,
1539
0
           WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
1540
1541
0
  if (rc & WL_LATCH_SET)
1542
0
    ResetLatch(MyLatch);
1543
0
}
1544
1545
/*
1546
 * Emit an error if a concurrent sync call is in progress.
1547
 * Otherwise, advertise that a sync is in progress.
1548
 */
1549
static void
1550
check_and_set_sync_info(pid_t sync_process_pid)
1551
0
{
1552
0
  SpinLockAcquire(&SlotSyncCtx->mutex);
1553
1554
  /*
1555
   * Exit immediately if promotion has been triggered.  This guards against
1556
   * a new worker (or a call to pg_sync_replication_slots()) that starts
1557
   * after the old worker was stopped by ShutDownSlotSync().
1558
   */
1559
0
  if (SlotSyncCtx->stopSignaled)
1560
0
  {
1561
0
    SpinLockRelease(&SlotSyncCtx->mutex);
1562
1563
0
    if (AmLogicalSlotSyncWorkerProcess())
1564
0
    {
1565
0
      ereport(DEBUG1,
1566
0
          errmsg("replication slot synchronization worker will not start because promotion was triggered"));
1567
1568
0
      proc_exit(0);
1569
0
    }
1570
0
    else
1571
0
    {
1572
      /*
1573
       * For the backend executing SQL function
1574
       * pg_sync_replication_slots().
1575
       */
1576
0
      ereport(ERROR,
1577
0
          errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1578
0
          errmsg("replication slot synchronization will not start because promotion was triggered"));
1579
0
    }
1580
0
  }
1581
1582
0
  if (SlotSyncCtx->syncing)
1583
0
  {
1584
0
    SpinLockRelease(&SlotSyncCtx->mutex);
1585
0
    ereport(ERROR,
1586
0
        errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1587
0
        errmsg("cannot synchronize replication slots concurrently"));
1588
0
  }
1589
1590
  /* The pid must not be already assigned in SlotSyncCtx */
1591
0
  Assert(SlotSyncCtx->pid == InvalidPid);
1592
1593
0
  SlotSyncCtx->syncing = true;
1594
1595
  /*
1596
   * Advertise the required PID so that the startup process can kill the
1597
   * slot sync process on promotion.
1598
   */
1599
0
  SlotSyncCtx->pid = sync_process_pid;
1600
1601
0
  SpinLockRelease(&SlotSyncCtx->mutex);
1602
1603
0
  syncing_slots = true;
1604
0
}
1605
1606
/*
1607
 * Reset syncing flag.
1608
 */
1609
static void
1610
reset_syncing_flag(void)
1611
0
{
1612
0
  SpinLockAcquire(&SlotSyncCtx->mutex);
1613
0
  SlotSyncCtx->syncing = false;
1614
0
  SlotSyncCtx->pid = InvalidPid;
1615
0
  SpinLockRelease(&SlotSyncCtx->mutex);
1616
1617
0
  syncing_slots = false;
1618
0
}
1619
1620
/*
1621
 * The main loop of our worker process.
1622
 *
1623
 * It connects to the primary server, fetches logical failover slots
1624
 * information periodically in order to create and sync the slots.
1625
 *
1626
 * Note: If any changes are made here, check if the corresponding SQL
1627
 * function logic in SyncReplicationSlots() also needs to be changed.
1628
 */
1629
void
1630
ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
1631
0
{
1632
0
  WalReceiverConn *wrconn = NULL;
1633
0
  char     *dbname;
1634
0
  char     *err;
1635
0
  sigjmp_buf  local_sigjmp_buf;
1636
0
  StringInfoData app_name;
1637
1638
0
  Assert(startup_data_len == 0);
1639
1640
  /* Release postmaster's working memory context */
1641
0
  if (PostmasterContext)
1642
0
  {
1643
0
    MemoryContextDelete(PostmasterContext);
1644
0
    PostmasterContext = NULL;
1645
0
  }
1646
1647
0
  init_ps_display(NULL);
1648
1649
0
  Assert(GetProcessingMode() == InitProcessing);
1650
1651
  /*
1652
   * Create a per-backend PGPROC struct in shared memory.  We must do this
1653
   * before we access any shared memory.
1654
   */
1655
0
  InitProcess();
1656
1657
  /*
1658
   * Early initialization.
1659
   */
1660
0
  BaseInit();
1661
1662
0
  Assert(SlotSyncCtx != NULL);
1663
1664
  /*
1665
   * If an exception is encountered, processing resumes here.
1666
   *
1667
   * We just need to clean up, report the error, and go away.
1668
   *
1669
   * If we do not have this handling here, then since this worker process
1670
   * operates at the bottom of the exception stack, ERRORs turn into FATALs.
1671
   * Therefore, we create our own exception handler to catch ERRORs.
1672
   */
1673
0
  if (sigsetjmp(local_sigjmp_buf, 1) != 0)
1674
0
  {
1675
    /* since not using PG_TRY, must reset error stack by hand */
1676
0
    error_context_stack = NULL;
1677
1678
    /* Prevents interrupts while cleaning up */
1679
0
    HOLD_INTERRUPTS();
1680
1681
    /* Report the error to the server log */
1682
0
    EmitErrorReport();
1683
1684
    /*
1685
     * We can now go away.  Note that because we called InitProcess, a
1686
     * callback was registered to do ProcKill, which will clean up
1687
     * necessary state.
1688
     */
1689
0
    proc_exit(0);
1690
0
  }
1691
1692
  /* We can now handle ereport(ERROR) */
1693
0
  PG_exception_stack = &local_sigjmp_buf;
1694
1695
  /* Setup signal handling */
1696
0
  pqsignal(SIGHUP, SignalHandlerForConfigReload);
1697
0
  pqsignal(SIGINT, StatementCancelHandler);
1698
0
  pqsignal(SIGTERM, die);
1699
0
  pqsignal(SIGFPE, FloatExceptionHandler);
1700
0
  pqsignal(SIGUSR1, procsignal_sigusr1_handler);
1701
0
  pqsignal(SIGUSR2, PG_SIG_IGN);
1702
0
  pqsignal(SIGPIPE, PG_SIG_IGN);
1703
1704
0
  check_and_set_sync_info(MyProcPid);
1705
1706
0
  ereport(LOG, errmsg("slot sync worker started"));
1707
1708
  /* Register it as soon as SlotSyncCtx->pid is initialized. */
1709
0
  before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
1710
1711
  /*
1712
   * Establishes SIGALRM handler and initialize timeout module. It is needed
1713
   * by InitPostgres to register different timeouts.
1714
   */
1715
0
  InitializeTimeouts();
1716
1717
  /* Load the libpq-specific functions */
1718
0
  load_file("libpqwalreceiver", false);
1719
1720
  /*
1721
   * Unblock signals (they were blocked when the postmaster forked us)
1722
   */
1723
0
  sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
1724
1725
  /*
1726
   * Set always-secure search path, so malicious users can't redirect user
1727
   * code (e.g. operators).
1728
   *
1729
   * It's not strictly necessary since we won't be scanning or writing to
1730
   * any user table locally, but it's good to retain it here for added
1731
   * precaution.
1732
   */
1733
0
  SetConfigOption("search_path", "", PGC_SUSET, PGC_S_OVERRIDE);
1734
1735
0
  dbname = CheckAndGetDbnameFromConninfo();
1736
1737
  /*
1738
   * Connect to the database specified by the user in primary_conninfo. We
1739
   * need a database connection for walrcv_exec to work which we use to
1740
   * fetch slot information from the remote node. See comments atop
1741
   * libpqrcv_exec.
1742
   *
1743
   * We do not specify a specific user here since the slot sync worker will
1744
   * operate as a superuser. This is safe because the slot sync worker does
1745
   * not interact with user tables, eliminating the risk of executing
1746
   * arbitrary code within triggers.
1747
   */
1748
0
  InitPostgres(dbname, InvalidOid, NULL, InvalidOid, 0, NULL);
1749
1750
0
  SetProcessingMode(NormalProcessing);
1751
1752
0
  initStringInfo(&app_name);
1753
0
  if (cluster_name[0])
1754
0
    appendStringInfo(&app_name, "%s_%s", cluster_name, "slotsync worker");
1755
0
  else
1756
0
    appendStringInfoString(&app_name, "slotsync worker");
1757
1758
  /*
1759
   * Establish the connection to the primary server for slot
1760
   * synchronization.
1761
   */
1762
0
  wrconn = walrcv_connect(PrimaryConnInfo, false, false, false,
1763
0
              app_name.data, &err);
1764
1765
0
  if (!wrconn)
1766
0
    ereport(ERROR,
1767
0
        errcode(ERRCODE_CONNECTION_FAILURE),
1768
0
        errmsg("synchronization worker \"%s\" could not connect to the primary server: %s",
1769
0
             app_name.data, err));
1770
1771
0
  pfree(app_name.data);
1772
1773
  /*
1774
   * Register the disconnection callback.
1775
   *
1776
   * XXX: This can be combined with previous cleanup registration of
1777
   * slotsync_worker_onexit() but that will need the connection to be made
1778
   * global and we want to avoid introducing global for this purpose.
1779
   */
1780
0
  before_shmem_exit(slotsync_worker_disconnect, PointerGetDatum(wrconn));
1781
1782
  /*
1783
   * Using the specified primary server connection, check that we are not a
1784
   * cascading standby and slot configured in 'primary_slot_name' exists on
1785
   * the primary server.
1786
   */
1787
0
  validate_remote_info(wrconn);
1788
1789
  /* Main loop to synchronize slots */
1790
0
  for (;;)
1791
0
  {
1792
0
    bool    some_slot_updated = false;
1793
0
    bool    started_tx = false;
1794
0
    List     *remote_slots;
1795
1796
0
    CHECK_FOR_INTERRUPTS();
1797
1798
0
    if (ConfigReloadPending)
1799
0
      slotsync_reread_config();
1800
1801
    /*
1802
     * The syscache access in fetch_remote_slots() needs a transaction
1803
     * env.
1804
     */
1805
0
    if (!IsTransactionState())
1806
0
    {
1807
0
      StartTransactionCommand();
1808
0
      started_tx = true;
1809
0
    }
1810
1811
0
    remote_slots = fetch_remote_slots(wrconn, NIL);
1812
0
    some_slot_updated = synchronize_slots(wrconn, remote_slots, NULL);
1813
0
    list_free_deep(remote_slots);
1814
1815
0
    if (started_tx)
1816
0
      CommitTransactionCommand();
1817
1818
0
    wait_for_slot_activity(some_slot_updated);
1819
0
  }
1820
1821
  /*
1822
   * The slot sync worker can't get here because it will only stop when it
1823
   * receives a stop request from the startup process, or when there is an
1824
   * error.
1825
   */
1826
0
  Assert(false);
1827
0
}
1828
1829
/*
1830
 * Update the inactive_since property for synced slots.
1831
 *
1832
 * Note that this function is currently called when we shutdown the slot
1833
 * sync machinery.
1834
 */
1835
static void
1836
update_synced_slots_inactive_since(void)
1837
0
{
1838
0
  TimestampTz now = 0;
1839
1840
  /*
1841
   * We need to update inactive_since only when we are promoting standby to
1842
   * correctly interpret the inactive_since if the standby gets promoted
1843
   * without a restart. We don't want the slots to appear inactive for a
1844
   * long time after promotion if they haven't been synchronized recently.
1845
   * Whoever acquires the slot, i.e., makes the slot active, will reset it.
1846
   */
1847
0
  if (!StandbyMode)
1848
0
    return;
1849
1850
  /* The slot sync worker or the SQL function mustn't be running by now */
1851
0
  Assert((SlotSyncCtx->pid == InvalidPid) && !SlotSyncCtx->syncing);
1852
1853
0
  LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
1854
1855
0
  for (int i = 0; i < max_replication_slots + max_repack_replication_slots; i++)
1856
0
  {
1857
0
    ReplicationSlot *s = &ReplicationSlotCtl->replication_slots[i];
1858
1859
    /* Check if it is a synchronized slot */
1860
0
    if (s->in_use && s->data.synced)
1861
0
    {
1862
0
      Assert(SlotIsLogical(s));
1863
1864
      /* The slot must not be acquired by any process */
1865
0
      Assert(s->active_proc == INVALID_PROC_NUMBER);
1866
1867
      /* Use the same inactive_since time for all the slots. */
1868
0
      if (now == 0)
1869
0
        now = GetCurrentTimestamp();
1870
1871
0
      ReplicationSlotSetInactiveSince(s, now, true);
1872
0
    }
1873
0
  }
1874
1875
0
  LWLockRelease(ReplicationSlotControlLock);
1876
0
}
1877
1878
/*
1879
 * Shut down slot synchronization.
1880
 *
1881
 * This function sets stopSignaled=true and wakes up the slot sync process
1882
 * (either worker or backend running the SQL function pg_sync_replication_slots())
1883
 * so that worker can exit or the SQL function pg_sync_replication_slots() can
1884
 * finish. It also waits till the slot sync worker has exited or
1885
 * pg_sync_replication_slots() has finished.
1886
 */
1887
void
1888
ShutDownSlotSync(void)
1889
0
{
1890
0
  pid_t   sync_process_pid;
1891
1892
0
  SpinLockAcquire(&SlotSyncCtx->mutex);
1893
1894
0
  SlotSyncCtx->stopSignaled = true;
1895
1896
  /*
1897
   * Return if neither the slot sync worker is running nor the function
1898
   * pg_sync_replication_slots() is executing.
1899
   */
1900
0
  if (!SlotSyncCtx->syncing)
1901
0
  {
1902
0
    SpinLockRelease(&SlotSyncCtx->mutex);
1903
0
    update_synced_slots_inactive_since();
1904
0
    return;
1905
0
  }
1906
1907
0
  sync_process_pid = SlotSyncCtx->pid;
1908
1909
0
  SpinLockRelease(&SlotSyncCtx->mutex);
1910
1911
  /*
1912
   * Signal process doing slotsync, if any, asking it to stop.
1913
   */
1914
0
  if (sync_process_pid != InvalidPid)
1915
0
    SendProcSignal(sync_process_pid, PROCSIG_SLOTSYNC_MESSAGE,
1916
0
             INVALID_PROC_NUMBER);
1917
1918
  /* Wait for slot sync to end */
1919
0
  for (;;)
1920
0
  {
1921
0
    int     rc;
1922
1923
    /* Wait a bit, we don't expect to have to wait long */
1924
0
    rc = WaitLatch(MyLatch,
1925
0
             WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
1926
0
             10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
1927
1928
0
    if (rc & WL_LATCH_SET)
1929
0
    {
1930
0
      ResetLatch(MyLatch);
1931
0
      CHECK_FOR_INTERRUPTS();
1932
0
    }
1933
1934
0
    SpinLockAcquire(&SlotSyncCtx->mutex);
1935
1936
    /* Ensure that no process is syncing the slots. */
1937
0
    if (!SlotSyncCtx->syncing)
1938
0
      break;
1939
1940
0
    SpinLockRelease(&SlotSyncCtx->mutex);
1941
0
  }
1942
1943
0
  SpinLockRelease(&SlotSyncCtx->mutex);
1944
1945
0
  update_synced_slots_inactive_since();
1946
0
}
1947
1948
/*
1949
 * SlotSyncWorkerCanRestart
1950
 *
1951
 * Return true, indicating worker is allowed to restart, if enough time has
1952
 * passed since it was last launched to reach SLOTSYNC_RESTART_INTERVAL_SEC.
1953
 * Otherwise return false.
1954
 *
1955
 * This is a safety valve to protect against continuous respawn attempts if the
1956
 * worker is dying immediately at launch. Note that since we will retry to
1957
 * launch the worker from the postmaster main loop, we will get another
1958
 * chance later.
1959
 */
1960
bool
1961
SlotSyncWorkerCanRestart(void)
1962
0
{
1963
0
  time_t    curtime = time(NULL);
1964
1965
  /*
1966
   * If first time through, or time somehow went backwards, always update
1967
   * last_start_time to match the current clock and allow worker start.
1968
   * Otherwise allow it only once enough time has elapsed.
1969
   */
1970
0
  if (SlotSyncCtx->last_start_time == 0 ||
1971
0
    curtime < SlotSyncCtx->last_start_time ||
1972
0
    curtime - SlotSyncCtx->last_start_time >= SLOTSYNC_RESTART_INTERVAL_SEC)
1973
0
  {
1974
0
    SlotSyncCtx->last_start_time = curtime;
1975
0
    return true;
1976
0
  }
1977
0
  return false;
1978
0
}
1979
1980
/*
1981
 * Is current process syncing replication slots?
1982
 *
1983
 * Could be either backend executing SQL function or slot sync worker.
1984
 */
1985
bool
1986
IsSyncingReplicationSlots(void)
1987
0
{
1988
0
  return syncing_slots;
1989
0
}
1990
1991
/*
1992
 * Register shared memory space needed for slot synchronization.
1993
 */
1994
static void
1995
SlotSyncShmemRequest(void *arg)
1996
0
{
1997
0
  ShmemRequestStruct(.name = "Slot Sync Data",
1998
0
             .size = sizeof(SlotSyncCtxStruct),
1999
0
             .ptr = (void **) &SlotSyncCtx,
2000
0
    );
2001
0
}
2002
2003
/*
2004
 * Initialize shared memory for slot synchronization.
2005
 */
2006
static void
2007
SlotSyncShmemInit(void *arg)
2008
0
{
2009
0
  memset(SlotSyncCtx, 0, sizeof(SlotSyncCtxStruct));
2010
0
  SlotSyncCtx->pid = InvalidPid;
2011
0
  SpinLockInit(&SlotSyncCtx->mutex);
2012
0
}
2013
2014
/*
2015
 * Error cleanup callback for slot sync SQL function.
2016
 */
2017
static void
2018
slotsync_failure_callback(int code, Datum arg)
2019
0
{
2020
0
  WalReceiverConn *wrconn = (WalReceiverConn *) DatumGetPointer(arg);
2021
2022
  /*
2023
   * We need to do slots cleanup here just like WalSndErrorCleanup() does.
2024
   *
2025
   * The startup process during promotion invokes ShutDownSlotSync() which
2026
   * waits for slot sync to finish and it does that by checking the
2027
   * 'syncing' flag. Thus the SQL function must be done with slots' release
2028
   * and cleanup to avoid any dangling temporary slots or active slots
2029
   * before it marks itself as finished syncing.
2030
   */
2031
2032
  /* Make sure active replication slots are released */
2033
0
  if (MyReplicationSlot != NULL)
2034
0
    ReplicationSlotRelease();
2035
2036
  /* Also cleanup the synced temporary slots. */
2037
0
  ReplicationSlotCleanup(true);
2038
2039
  /*
2040
   * The set syncing_slots indicates that the process errored out without
2041
   * resetting the flag. So, we need to clean up shared memory and reset the
2042
   * flag here.
2043
   */
2044
0
  if (syncing_slots)
2045
0
    reset_syncing_flag();
2046
2047
0
  walrcv_disconnect(wrconn);
2048
0
}
2049
2050
/*
2051
 * Helper function to extract slot names from a list of remote slots
2052
 */
2053
static List *
2054
extract_slot_names(List *remote_slots)
2055
0
{
2056
0
  List     *slot_names = NIL;
2057
2058
0
  foreach_ptr(RemoteSlot, remote_slot, remote_slots)
2059
0
  {
2060
0
    char     *slot_name;
2061
2062
0
    slot_name = pstrdup(remote_slot->name);
2063
0
    slot_names = lappend(slot_names, slot_name);
2064
0
  }
2065
2066
0
  return slot_names;
2067
0
}
2068
2069
/*
2070
 * Synchronize the failover enabled replication slots using the specified
2071
 * primary server connection.
2072
 *
2073
 * Repeatedly fetches and updates replication slot information from the
2074
 * primary until all slots are at least "sync ready".
2075
 *
2076
 * Exits early if promotion is triggered or certain critical
2077
 * configuration parameters have changed.
2078
 */
2079
void
2080
SyncReplicationSlots(WalReceiverConn *wrconn)
2081
0
{
2082
0
  PG_ENSURE_ERROR_CLEANUP(slotsync_failure_callback, PointerGetDatum(wrconn));
2083
0
  {
2084
0
    List     *remote_slots = NIL;
2085
0
    List     *slot_names = NIL; /* List of slot names to track */
2086
0
    MemoryContext sync_retry_ctx;
2087
2088
0
    check_and_set_sync_info(MyProcPid);
2089
2090
0
    validate_remote_info(wrconn);
2091
2092
    /*
2093
     * Setup and use a per-sync-cycle memory context, which is reset every
2094
     * time we loop below. This avoids having to retail freeing the memory
2095
     * used in each sync cycle.
2096
     */
2097
0
    sync_retry_ctx = AllocSetContextCreate(CurrentMemoryContext,
2098
0
                         "slot sync retry context",
2099
0
                         ALLOCSET_DEFAULT_SIZES);
2100
2101
    /* Retry until all the slots are sync-ready */
2102
0
    for (;;)
2103
0
    {
2104
0
      bool    slot_persistence_pending = false;
2105
0
      bool    some_slot_updated = false;
2106
0
      MemoryContext oldctx;
2107
2108
      /* Check for interrupts and config changes */
2109
0
      CHECK_FOR_INTERRUPTS();
2110
2111
0
      if (ConfigReloadPending)
2112
0
        slotsync_reread_config();
2113
2114
      /* We must be in a valid transaction state */
2115
0
      Assert(IsTransactionState());
2116
2117
0
      MemoryContextReset(sync_retry_ctx);
2118
0
      oldctx = MemoryContextSwitchTo(sync_retry_ctx);
2119
2120
      /*
2121
       * Fetch remote slot info for the given slot_names. If slot_names
2122
       * is NIL, fetch all failover-enabled slots. Note that we reuse
2123
       * slot_names from the first iteration; re-fetching all failover
2124
       * slots each time could cause an endless loop. Instead of
2125
       * reprocessing only the pending slots in each iteration, it's
2126
       * better to process all the slots received in the first
2127
       * iteration. This ensures that by the time we're done, all slots
2128
       * reflect the latest values.
2129
       */
2130
0
      remote_slots = fetch_remote_slots(wrconn, slot_names);
2131
2132
      /* Attempt to synchronize slots */
2133
0
      some_slot_updated = synchronize_slots(wrconn, remote_slots,
2134
0
                          &slot_persistence_pending);
2135
2136
      /*
2137
       * slot_names must survive later sync_retry_ctx resets, so copy it
2138
       * in the outer context.
2139
       */
2140
0
      MemoryContextSwitchTo(oldctx);
2141
2142
      /*
2143
       * If slot_persistence_pending is true, extract slot names for
2144
       * future iterations (only needed if we haven't done it yet)
2145
       */
2146
0
      if (slot_names == NIL && slot_persistence_pending)
2147
0
        slot_names = extract_slot_names(remote_slots);
2148
2149
      /* Done if all slots are persisted i.e are sync-ready */
2150
0
      if (!slot_persistence_pending)
2151
0
        break;
2152
2153
      /* wait before retrying again */
2154
0
      wait_for_slot_activity(some_slot_updated);
2155
0
    }
2156
2157
0
    MemoryContextDelete(sync_retry_ctx);
2158
2159
0
    if (slot_names)
2160
0
      list_free_deep(slot_names);
2161
2162
    /* Cleanup the synced temporary slots */
2163
0
    ReplicationSlotCleanup(true);
2164
2165
    /* We are done with sync, so reset sync flag */
2166
0
    reset_syncing_flag();
2167
0
  }
2168
0
  PG_END_ENSURE_ERROR_CLEANUP(slotsync_failure_callback, PointerGetDatum(wrconn));
2169
0
}