/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 | } |