/src/postgres/src/backend/access/transam/xlogrecovery.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * xlogrecovery.c |
4 | | * Functions for WAL recovery, standby mode |
5 | | * |
6 | | * This source file contains functions controlling WAL recovery. |
7 | | * InitWalRecovery() initializes the system for crash or archive recovery, |
8 | | * or standby mode, depending on configuration options and the state of |
9 | | * the control file and possible backup label file. PerformWalRecovery() |
10 | | * performs the actual WAL replay, calling the rmgr-specific redo routines. |
11 | | * FinishWalRecovery() performs end-of-recovery checks and cleanup actions, |
12 | | * and prepares information needed to initialize the WAL for writes. In |
13 | | * addition to these three main functions, there are a bunch of functions |
14 | | * for interrogating recovery state and controlling the recovery process. |
15 | | * |
16 | | * |
17 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
18 | | * Portions Copyright (c) 1994, Regents of the University of California |
19 | | * |
20 | | * src/backend/access/transam/xlogrecovery.c |
21 | | * |
22 | | *------------------------------------------------------------------------- |
23 | | */ |
24 | | |
25 | | #include "postgres.h" |
26 | | |
27 | | #include <ctype.h> |
28 | | #include <time.h> |
29 | | #include <sys/stat.h> |
30 | | #include <sys/time.h> |
31 | | #include <unistd.h> |
32 | | |
33 | | #include "access/timeline.h" |
34 | | #include "access/transam.h" |
35 | | #include "access/xact.h" |
36 | | #include "access/xlog_internal.h" |
37 | | #include "access/xlogarchive.h" |
38 | | #include "access/xlogprefetcher.h" |
39 | | #include "access/xlogreader.h" |
40 | | #include "access/xlogrecovery.h" |
41 | | #include "access/xlogutils.h" |
42 | | #include "access/xlogwait.h" |
43 | | #include "backup/basebackup.h" |
44 | | #include "catalog/pg_control.h" |
45 | | #include "commands/tablespace.h" |
46 | | #include "common/file_utils.h" |
47 | | #include "common/pg_parse_lsn.h" |
48 | | #include "miscadmin.h" |
49 | | #include "nodes/miscnodes.h" |
50 | | #include "pgstat.h" |
51 | | #include "postmaster/bgwriter.h" |
52 | | #include "postmaster/startup.h" |
53 | | #include "replication/slot.h" |
54 | | #include "replication/slotsync.h" |
55 | | #include "replication/walreceiver.h" |
56 | | #include "storage/fd.h" |
57 | | #include "storage/ipc.h" |
58 | | #include "storage/latch.h" |
59 | | #include "storage/pmsignal.h" |
60 | | #include "storage/procarray.h" |
61 | | #include "storage/spin.h" |
62 | | #include "storage/subsystems.h" |
63 | | #include "utils/datetime.h" |
64 | | #include "utils/fmgrprotos.h" |
65 | | #include "utils/guc.h" |
66 | | #include "utils/guc_hooks.h" |
67 | | #include "utils/pgstat_internal.h" |
68 | | #include "utils/pg_lsn.h" |
69 | | #include "utils/ps_status.h" |
70 | | #include "utils/pg_rusage.h" |
71 | | #include "utils/wait_event.h" |
72 | | |
73 | | /* Unsupported old recovery command file names (relative to $PGDATA) */ |
74 | 0 | #define RECOVERY_COMMAND_FILE "recovery.conf" |
75 | 0 | #define RECOVERY_COMMAND_DONE "recovery.done" |
76 | | |
77 | | /* |
78 | | * GUC support |
79 | | */ |
80 | | const struct config_enum_entry recovery_target_action_options[] = { |
81 | | {"pause", RECOVERY_TARGET_ACTION_PAUSE, false}, |
82 | | {"promote", RECOVERY_TARGET_ACTION_PROMOTE, false}, |
83 | | {"shutdown", RECOVERY_TARGET_ACTION_SHUTDOWN, false}, |
84 | | {NULL, 0, false} |
85 | | }; |
86 | | |
87 | | /* options formerly taken from recovery.conf for archive recovery */ |
88 | | char *recoveryRestoreCommand = NULL; |
89 | | char *recoveryEndCommand = NULL; |
90 | | char *archiveCleanupCommand = NULL; |
91 | | RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET; |
92 | | bool recoveryTargetInclusive = true; |
93 | | int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE; |
94 | | TransactionId recoveryTargetXid; |
95 | | char *recovery_target_time_string; |
96 | | TimestampTz recoveryTargetTime; |
97 | | char *recoveryTargetName; |
98 | | XLogRecPtr recoveryTargetLSN; |
99 | | int recovery_min_apply_delay = 0; |
100 | | |
101 | | /* options formerly taken from recovery.conf for XLOG streaming */ |
102 | | char *PrimaryConnInfo = NULL; |
103 | | char *PrimarySlotName = NULL; |
104 | | bool wal_receiver_create_temp_slot = false; |
105 | | |
106 | | /* |
107 | | * recoveryTargetTimeLineGoal: what the user requested, if any |
108 | | * |
109 | | * recoveryTargetTLIRequested: numeric value of requested timeline, if constant |
110 | | * |
111 | | * recoveryTargetTLI: the currently understood target timeline; changes |
112 | | * |
113 | | * expectedTLEs: a list of TimeLineHistoryEntries for recoveryTargetTLI and |
114 | | * the timelines of its known parents, newest first (so recoveryTargetTLI is |
115 | | * always the first list member). Only these TLIs are expected to be seen in |
116 | | * the WAL segments we read, and indeed only these TLIs will be considered as |
117 | | * candidate WAL files to open at all. |
118 | | * |
119 | | * curFileTLI: the TLI appearing in the name of the current input WAL file. |
120 | | * (This is not necessarily the same as the timeline from which we are |
121 | | * replaying WAL, which StartupXLOG calls replayTLI, because we could be |
122 | | * scanning data that was copied from an ancestor timeline when the current |
123 | | * file was created.) During a sequential scan we do not allow this value |
124 | | * to decrease. |
125 | | */ |
126 | | RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_LATEST; |
127 | | TimeLineID recoveryTargetTLIRequested = 0; |
128 | | TimeLineID recoveryTargetTLI = 0; |
129 | | static List *expectedTLEs; |
130 | | static TimeLineID curFileTLI; |
131 | | |
132 | | /* |
133 | | * When ArchiveRecoveryRequested is set, archive recovery was requested, |
134 | | * ie. signal files were present. When InArchiveRecovery is set, we are |
135 | | * currently recovering using offline XLOG archives. These variables are only |
136 | | * valid in the startup process. |
137 | | * |
138 | | * When ArchiveRecoveryRequested is true, but InArchiveRecovery is false, we're |
139 | | * currently performing crash recovery using only XLOG files in pg_wal, but |
140 | | * will switch to using offline XLOG archives as soon as we reach the end of |
141 | | * WAL in pg_wal. |
142 | | */ |
143 | | bool ArchiveRecoveryRequested = false; |
144 | | bool InArchiveRecovery = false; |
145 | | |
146 | | /* |
147 | | * When StandbyModeRequested is set, standby mode was requested, i.e. |
148 | | * standby.signal file was present. When StandbyMode is set, we are currently |
149 | | * in standby mode. These variables are only valid in the startup process. |
150 | | * They work similarly to ArchiveRecoveryRequested and InArchiveRecovery. |
151 | | */ |
152 | | static bool StandbyModeRequested = false; |
153 | | bool StandbyMode = false; |
154 | | |
155 | | /* was a signal file present at startup? */ |
156 | | static bool standby_signal_file_found = false; |
157 | | static bool recovery_signal_file_found = false; |
158 | | |
159 | | /* |
160 | | * CheckPointLoc is the position of the checkpoint record that determines |
161 | | * where to start the replay. It comes from the backup label file or the |
162 | | * control file. |
163 | | * |
164 | | * RedoStartLSN is the checkpoint's REDO location, also from the backup label |
165 | | * file or the control file. In standby mode, XLOG streaming usually starts |
166 | | * from the position where an invalid record was found. But if we fail to |
167 | | * read even the initial checkpoint record, we use the REDO location instead |
168 | | * of the checkpoint location as the start position of XLOG streaming. |
169 | | * Otherwise we would have to jump backwards to the REDO location after |
170 | | * reading the checkpoint record, because the REDO record can precede the |
171 | | * checkpoint record. |
172 | | */ |
173 | | static XLogRecPtr CheckPointLoc = InvalidXLogRecPtr; |
174 | | static TimeLineID CheckPointTLI = 0; |
175 | | static XLogRecPtr RedoStartLSN = InvalidXLogRecPtr; |
176 | | static TimeLineID RedoStartTLI = 0; |
177 | | |
178 | | /* |
179 | | * Local copy of SharedHotStandbyActive variable. False actually means "not |
180 | | * known, need to check the shared state". |
181 | | */ |
182 | | static bool LocalHotStandbyActive = false; |
183 | | |
184 | | /* |
185 | | * Local copy of SharedPromoteIsTriggered variable. False actually means "not |
186 | | * known, need to check the shared state". |
187 | | */ |
188 | | static bool LocalPromoteIsTriggered = false; |
189 | | |
190 | | /* Has the recovery code requested a walreceiver wakeup? */ |
191 | | static bool doRequestWalReceiverReply; |
192 | | |
193 | | /* XLogReader object used to parse the WAL records */ |
194 | | static XLogReaderState *xlogreader = NULL; |
195 | | |
196 | | /* XLogPrefetcher object used to consume WAL records with read-ahead */ |
197 | | static XLogPrefetcher *xlogprefetcher = NULL; |
198 | | |
199 | | /* Parameters passed down from ReadRecord to the XLogPageRead callback. */ |
200 | | typedef struct XLogPageReadPrivate |
201 | | { |
202 | | int emode; |
203 | | bool fetching_ckpt; /* are we fetching a checkpoint record? */ |
204 | | bool randAccess; |
205 | | TimeLineID replayTLI; |
206 | | } XLogPageReadPrivate; |
207 | | |
208 | | /* flag to tell XLogPageRead that we have started replaying */ |
209 | | static bool InRedo = false; |
210 | | |
211 | | /* |
212 | | * Codes indicating where we got a WAL file from during recovery, or where |
213 | | * to attempt to get one. |
214 | | */ |
215 | | typedef enum |
216 | | { |
217 | | XLOG_FROM_ANY = 0, /* request to read WAL from any source */ |
218 | | XLOG_FROM_ARCHIVE, /* restored using restore_command */ |
219 | | XLOG_FROM_PG_WAL, /* existing file in pg_wal */ |
220 | | XLOG_FROM_STREAM, /* streamed from primary */ |
221 | | } XLogSource; |
222 | | |
223 | | /* human-readable names for XLogSources, for debugging output */ |
224 | | static const char *const xlogSourceNames[] = {"any", "archive", "pg_wal", "stream"}; |
225 | | |
226 | | /* |
227 | | * readFile is -1 or a kernel FD for the log file segment that's currently |
228 | | * open for reading. readSegNo identifies the segment. readOff is the offset |
229 | | * of the page just read, readLen indicates how much of it has been read into |
230 | | * readBuf, and readSource indicates where we got the currently open file from. |
231 | | * |
232 | | * Note: we could use Reserve/ReleaseExternalFD to track consumption of this |
233 | | * FD too (like for openLogFile in xlog.c); but it doesn't currently seem |
234 | | * worthwhile, since the XLOG is not read by general-purpose sessions. |
235 | | */ |
236 | | static int readFile = -1; |
237 | | static XLogSegNo readSegNo = 0; |
238 | | static uint32 readOff = 0; |
239 | | static uint32 readLen = 0; |
240 | | static XLogSource readSource = XLOG_FROM_ANY; |
241 | | |
242 | | /* |
243 | | * Keeps track of which source we're currently reading from. This is |
244 | | * different from readSource in that this is always set, even when we don't |
245 | | * currently have a WAL file open. If lastSourceFailed is set, our last |
246 | | * attempt to read from currentSource failed, and we should try another source |
247 | | * next. |
248 | | * |
249 | | * pendingWalRcvRestart is set when a config change occurs that requires a |
250 | | * walreceiver restart. This is only valid in XLOG_FROM_STREAM state. |
251 | | */ |
252 | | static XLogSource currentSource = XLOG_FROM_ANY; |
253 | | static bool lastSourceFailed = false; |
254 | | static bool pendingWalRcvRestart = false; |
255 | | |
256 | | /* |
257 | | * These variables track when we last obtained some WAL data to process, |
258 | | * and where we got it from. (XLogReceiptSource is initially the same as |
259 | | * readSource, but readSource gets reset to zero when we don't have data |
260 | | * to process right now. It is also different from currentSource, which |
261 | | * also changes when we try to read from a source and fail, while |
262 | | * XLogReceiptSource tracks where we last successfully read some WAL.) |
263 | | */ |
264 | | static TimestampTz XLogReceiptTime = 0; |
265 | | static XLogSource XLogReceiptSource = XLOG_FROM_ANY; |
266 | | |
267 | | /* Local copy of WalRcv->flushedUpto */ |
268 | | static XLogRecPtr flushedUpto = InvalidXLogRecPtr; |
269 | | static TimeLineID receiveTLI = 0; |
270 | | |
271 | | /* |
272 | | * Copy of minRecoveryPoint and backupEndPoint from the control file. |
273 | | * |
274 | | * In order to reach consistency, we must replay the WAL up to |
275 | | * minRecoveryPoint. If backupEndRequired is true, we must also reach |
276 | | * backupEndPoint, or if it's invalid, an end-of-backup record corresponding |
277 | | * to backupStartPoint. |
278 | | * |
279 | | * Note: In archive recovery, after consistency has been reached, the |
280 | | * functions in xlog.c will start updating minRecoveryPoint in the control |
281 | | * file. But this copy of minRecoveryPoint variable reflects the value at the |
282 | | * beginning of recovery, and is *not* updated after consistency is reached. |
283 | | */ |
284 | | static XLogRecPtr minRecoveryPoint; |
285 | | static TimeLineID minRecoveryPointTLI; |
286 | | |
287 | | static XLogRecPtr backupStartPoint; |
288 | | static XLogRecPtr backupEndPoint; |
289 | | static bool backupEndRequired = false; |
290 | | |
291 | | /* |
292 | | * Have we reached a consistent database state? In crash recovery, we have |
293 | | * to replay all the WAL, so reachedConsistency is never set. During archive |
294 | | * recovery, the database is consistent once minRecoveryPoint is reached. |
295 | | * |
296 | | * Consistent state means that the system is internally consistent, all |
297 | | * the WAL has been replayed up to a certain point, and importantly, there |
298 | | * is no trace of later actions on disk. |
299 | | * |
300 | | * This flag is used only by the startup process and postmaster. When |
301 | | * minRecoveryPoint is reached, the startup process sets it to true and |
302 | | * sends a PMSIGNAL_RECOVERY_CONSISTENT signal to the postmaster, |
303 | | * which then sets it to true upon receiving the signal. |
304 | | */ |
305 | | bool reachedConsistency = false; |
306 | | |
307 | | /* Buffers dedicated to consistency checks of size BLCKSZ */ |
308 | | static char *replay_image_masked = NULL; |
309 | | static char *primary_image_masked = NULL; |
310 | | |
311 | | XLogRecoveryCtlData *XLogRecoveryCtl = NULL; |
312 | | |
313 | | static void XLogRecoveryShmemRequest(void *arg); |
314 | | static void XLogRecoveryShmemInit(void *arg); |
315 | | |
316 | | const ShmemCallbacks XLogRecoveryShmemCallbacks = { |
317 | | .request_fn = XLogRecoveryShmemRequest, |
318 | | .init_fn = XLogRecoveryShmemInit, |
319 | | }; |
320 | | |
321 | | /* |
322 | | * abortedRecPtr is the start pointer of a broken record at end of WAL when |
323 | | * recovery completes; missingContrecPtr is the location of the first |
324 | | * contrecord that went missing. See CreateOverwriteContrecordRecord for |
325 | | * details. |
326 | | */ |
327 | | static XLogRecPtr abortedRecPtr; |
328 | | static XLogRecPtr missingContrecPtr; |
329 | | |
330 | | /* |
331 | | * if recoveryStopsBefore/After returns true, it saves information of the stop |
332 | | * point here |
333 | | */ |
334 | | static TransactionId recoveryStopXid; |
335 | | static TimestampTz recoveryStopTime; |
336 | | static XLogRecPtr recoveryStopLSN; |
337 | | static char recoveryStopName[MAXFNAMELEN]; |
338 | | static bool recoveryStopAfter; |
339 | | |
340 | | /* prototypes for local functions */ |
341 | | static void ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *replayTLI); |
342 | | |
343 | | static void EnableStandbyMode(void); |
344 | | static void readRecoverySignalFile(void); |
345 | | static void validateRecoveryParameters(void); |
346 | | static bool read_backup_label(XLogRecPtr *checkPointLoc, |
347 | | TimeLineID *backupLabelTLI, |
348 | | bool *backupEndRequired, bool *backupFromStandby); |
349 | | static bool read_tablespace_map(List **tablespaces); |
350 | | |
351 | | static void xlogrecovery_redo(XLogReaderState *record, TimeLineID replayTLI); |
352 | | static void CheckRecoveryConsistency(void); |
353 | | static void rm_redo_error_callback(void *arg); |
354 | | #ifdef WAL_DEBUG |
355 | | static void xlog_outrec(StringInfo buf, XLogReaderState *record); |
356 | | #endif |
357 | | static void xlog_block_info(StringInfo buf, XLogReaderState *record); |
358 | | static void checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI, |
359 | | TimeLineID prevTLI, TimeLineID replayTLI); |
360 | | static bool getRecordTimestamp(XLogReaderState *record, TimestampTz *recordXtime); |
361 | | static void verifyBackupPageConsistency(XLogReaderState *record); |
362 | | |
363 | | static bool recoveryStopsBefore(XLogReaderState *record); |
364 | | static bool recoveryStopsAfter(XLogReaderState *record); |
365 | | static char *getRecoveryStopReason(void); |
366 | | static void recoveryPausesHere(bool endOfRecovery); |
367 | | static bool recoveryApplyDelay(XLogReaderState *record); |
368 | | static void ConfirmRecoveryPaused(void); |
369 | | |
370 | | static XLogRecord *ReadRecord(XLogPrefetcher *xlogprefetcher, |
371 | | int emode, bool fetching_ckpt, |
372 | | TimeLineID replayTLI); |
373 | | |
374 | | static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, |
375 | | int reqLen, XLogRecPtr targetRecPtr, char *readBuf); |
376 | | static XLogPageReadResult WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, |
377 | | bool randAccess, |
378 | | bool fetching_ckpt, |
379 | | XLogRecPtr tliRecPtr, |
380 | | TimeLineID replayTLI, |
381 | | XLogRecPtr replayLSN, |
382 | | bool nonblocking); |
383 | | static int emode_for_corrupt_record(int emode, XLogRecPtr RecPtr); |
384 | | static XLogRecord *ReadCheckpointRecord(XLogPrefetcher *xlogprefetcher, |
385 | | XLogRecPtr RecPtr, TimeLineID replayTLI); |
386 | | static bool rescanLatestTimeLine(TimeLineID replayTLI, XLogRecPtr replayLSN); |
387 | | static int XLogFileRead(XLogSegNo segno, TimeLineID tli, |
388 | | XLogSource source, bool notfoundOk); |
389 | | static int XLogFileReadAnyTLI(XLogSegNo segno, XLogSource source); |
390 | | |
391 | | static bool CheckForStandbyTrigger(void); |
392 | | static void SetPromoteIsTriggered(void); |
393 | | static bool HotStandbyActiveInReplay(void); |
394 | | |
395 | | static void SetCurrentChunkStartTime(TimestampTz xtime); |
396 | | static void SetLatestXTime(TimestampTz xtime); |
397 | | static RecoveryTargetType DetermineRecoveryTargetType(void); |
398 | | |
399 | | /* |
400 | | * Register shared memory for WAL recovery |
401 | | */ |
402 | | static void |
403 | | XLogRecoveryShmemRequest(void *arg) |
404 | 0 | { |
405 | 0 | ShmemRequestStruct(.name = "XLOG Recovery Ctl", |
406 | 0 | .size = sizeof(XLogRecoveryCtlData), |
407 | 0 | .ptr = (void **) &XLogRecoveryCtl, |
408 | 0 | ); |
409 | 0 | } |
410 | | |
411 | | static void |
412 | | XLogRecoveryShmemInit(void *arg) |
413 | 0 | { |
414 | 0 | memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData)); |
415 | |
|
416 | 0 | SpinLockInit(&XLogRecoveryCtl->info_lck); |
417 | 0 | InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
418 | 0 | ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV); |
419 | 0 | } |
420 | | |
421 | | /* |
422 | | * A thin wrapper to enable StandbyMode and do other preparatory work as |
423 | | * needed. |
424 | | */ |
425 | | static void |
426 | | EnableStandbyMode(void) |
427 | 0 | { |
428 | 0 | StandbyMode = true; |
429 | | |
430 | | /* |
431 | | * To avoid server log bloat, we don't report recovery progress in a |
432 | | * standby as it will always be in recovery unless promoted. We disable |
433 | | * startup progress timeout in standby mode to avoid calling |
434 | | * startup_progress_timeout_handler() unnecessarily. |
435 | | */ |
436 | 0 | disable_startup_progress_timeout(); |
437 | 0 | } |
438 | | |
439 | | /* |
440 | | * Prepare the system for WAL recovery, if needed. |
441 | | * |
442 | | * This is called by StartupXLOG() which coordinates the server startup |
443 | | * sequence. This function analyzes the control file and the backup label |
444 | | * file, if any, and figures out whether we need to perform crash recovery or |
445 | | * archive recovery, and how far we need to replay the WAL to reach a |
446 | | * consistent state. |
447 | | * |
448 | | * This doesn't yet change the on-disk state, except for creating the symlinks |
449 | | * from table space map file if any, and for fetching WAL files needed to find |
450 | | * the checkpoint record. On entry, the caller has already read the control |
451 | | * file into memory, and passes it as argument. This function updates it to |
452 | | * reflect the recovery state, and the caller is expected to write it back to |
453 | | * disk after initializing other subsystems, but before calling |
454 | | * PerformWalRecovery(). |
455 | | * |
456 | | * This initializes some global variables like ArchiveRecoveryRequested, and |
457 | | * StandbyModeRequested and InRecovery. |
458 | | */ |
459 | | void |
460 | | InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr, |
461 | | bool *haveBackupLabel_ptr, bool *haveTblspcMap_ptr) |
462 | 0 | { |
463 | 0 | XLogPageReadPrivate *private; |
464 | 0 | struct stat st; |
465 | 0 | bool wasShutdown; |
466 | 0 | XLogRecord *record; |
467 | 0 | DBState dbstate_at_startup; |
468 | 0 | bool haveTblspcMap = false; |
469 | 0 | bool haveBackupLabel = false; |
470 | 0 | CheckPoint checkPoint; |
471 | 0 | bool backupFromStandby = false; |
472 | |
|
473 | 0 | dbstate_at_startup = ControlFile->state; |
474 | | |
475 | | /* |
476 | | * A startup process always starts with an inconsistent database. Set the |
477 | | * flag accordingly, even if it was inherited from a postmaster that had |
478 | | * already marked the database as consistent. This keeps the invariant |
479 | | * local to the startup process without requiring every fork path to clear |
480 | | * the flag. |
481 | | */ |
482 | 0 | reachedConsistency = false; |
483 | | |
484 | | /* |
485 | | * Initialize on the assumption we want to recover to the latest timeline |
486 | | * that's active according to pg_control. |
487 | | */ |
488 | 0 | if (ControlFile->minRecoveryPointTLI > |
489 | 0 | ControlFile->checkPointCopy.ThisTimeLineID) |
490 | 0 | recoveryTargetTLI = ControlFile->minRecoveryPointTLI; |
491 | 0 | else |
492 | 0 | recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID; |
493 | | |
494 | | /* |
495 | | * Check for signal files, and if so set up state for offline recovery |
496 | | */ |
497 | 0 | readRecoverySignalFile(); |
498 | 0 | validateRecoveryParameters(); |
499 | | |
500 | | /* |
501 | | * Take ownership of the wakeup latch if we're going to sleep during |
502 | | * recovery, if required. |
503 | | */ |
504 | 0 | if (ArchiveRecoveryRequested) |
505 | 0 | OwnLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
506 | | |
507 | | /* |
508 | | * Set the WAL reading processor now, as it will be needed when reading |
509 | | * the checkpoint record required (backup_label or not). |
510 | | */ |
511 | 0 | private = palloc0_object(XLogPageReadPrivate); |
512 | 0 | xlogreader = |
513 | 0 | XLogReaderAllocate(wal_segment_size, NULL, |
514 | 0 | XL_ROUTINE(.page_read = &XLogPageRead, |
515 | 0 | .segment_open = NULL, |
516 | 0 | .segment_close = wal_segment_close), |
517 | 0 | private); |
518 | 0 | if (!xlogreader) |
519 | 0 | ereport(ERROR, |
520 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
521 | 0 | errmsg("out of memory"), |
522 | 0 | errdetail("Failed while allocating a WAL reading processor."))); |
523 | 0 | xlogreader->system_identifier = ControlFile->system_identifier; |
524 | | |
525 | | /* |
526 | | * Set the WAL decode buffer size. This limits how far ahead we can read |
527 | | * in the WAL. |
528 | | */ |
529 | 0 | XLogReaderSetDecodeBuffer(xlogreader, NULL, wal_decode_buffer_size); |
530 | | |
531 | | /* Create a WAL prefetcher. */ |
532 | 0 | xlogprefetcher = XLogPrefetcherAllocate(xlogreader); |
533 | | |
534 | | /* |
535 | | * Allocate two page buffers dedicated to WAL consistency checks. We do |
536 | | * it this way, rather than just making static arrays, for two reasons: |
537 | | * (1) no need to waste the storage in most instantiations of the backend; |
538 | | * (2) a static char array isn't guaranteed to have any particular |
539 | | * alignment, whereas palloc() will provide MAXALIGN'd storage. |
540 | | */ |
541 | 0 | replay_image_masked = (char *) palloc(BLCKSZ); |
542 | 0 | primary_image_masked = (char *) palloc(BLCKSZ); |
543 | | |
544 | | /* |
545 | | * Read the backup_label file. We want to run this part of the recovery |
546 | | * process after checking for signal files and after performing validation |
547 | | * of the recovery parameters. |
548 | | */ |
549 | 0 | if (read_backup_label(&CheckPointLoc, &CheckPointTLI, &backupEndRequired, |
550 | 0 | &backupFromStandby)) |
551 | 0 | { |
552 | 0 | List *tablespaces = NIL; |
553 | | |
554 | | /* |
555 | | * Archive recovery was requested, and thanks to the backup label |
556 | | * file, we know how far we need to replay to reach consistency. Enter |
557 | | * archive recovery directly. |
558 | | */ |
559 | 0 | InArchiveRecovery = true; |
560 | 0 | if (StandbyModeRequested) |
561 | 0 | EnableStandbyMode(); |
562 | | |
563 | | /* |
564 | | * Omitting backup_label when creating a new replica, PITR node etc. |
565 | | * unfortunately is a common cause of corruption. Logging that |
566 | | * backup_label was used makes it a bit easier to exclude that as the |
567 | | * cause of observed corruption. |
568 | | * |
569 | | * Do so before we try to read the checkpoint record (which can fail), |
570 | | * as otherwise it can be hard to understand why a checkpoint other |
571 | | * than ControlFile->checkPoint is used. |
572 | | */ |
573 | 0 | ereport(LOG, |
574 | 0 | errmsg("starting backup recovery with redo LSN %X/%08X, checkpoint LSN %X/%08X, on timeline ID %u", |
575 | 0 | LSN_FORMAT_ARGS(RedoStartLSN), |
576 | 0 | LSN_FORMAT_ARGS(CheckPointLoc), |
577 | 0 | CheckPointTLI)); |
578 | | |
579 | | /* |
580 | | * When a backup_label file is present, we want to roll forward from |
581 | | * the checkpoint it identifies, rather than using pg_control. |
582 | | */ |
583 | 0 | record = ReadCheckpointRecord(xlogprefetcher, CheckPointLoc, |
584 | 0 | CheckPointTLI); |
585 | 0 | if (record != NULL) |
586 | 0 | { |
587 | 0 | memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint)); |
588 | 0 | wasShutdown = ((record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN); |
589 | 0 | ereport(DEBUG1, |
590 | 0 | errmsg_internal("checkpoint record is at %X/%08X", |
591 | 0 | LSN_FORMAT_ARGS(CheckPointLoc))); |
592 | 0 | InRecovery = true; /* force recovery even if SHUTDOWNED */ |
593 | | |
594 | | /* |
595 | | * Make sure that REDO location exists. This may not be the case |
596 | | * if there was a crash during an online backup, which left a |
597 | | * backup_label around that references a WAL segment that's |
598 | | * already been archived. |
599 | | */ |
600 | 0 | if (checkPoint.redo < CheckPointLoc) |
601 | 0 | { |
602 | 0 | XLogPrefetcherBeginRead(xlogprefetcher, checkPoint.redo); |
603 | 0 | if (!ReadRecord(xlogprefetcher, LOG, false, |
604 | 0 | checkPoint.ThisTimeLineID)) |
605 | 0 | ereport(FATAL, |
606 | 0 | errmsg("could not find redo location %X/%08X referenced by checkpoint record at %X/%08X", |
607 | 0 | LSN_FORMAT_ARGS(checkPoint.redo), LSN_FORMAT_ARGS(CheckPointLoc)), |
608 | 0 | errhint("If you are restoring from a backup, touch \"%s/recovery.signal\" or \"%s/standby.signal\" and add required recovery options.\n" |
609 | 0 | "If you are not restoring from a backup, try removing the file \"%s/backup_label\".\n" |
610 | 0 | "Be careful: removing \"%s/backup_label\" will result in a corrupt cluster if restoring from a backup.", |
611 | 0 | DataDir, DataDir, DataDir, DataDir)); |
612 | 0 | } |
613 | 0 | } |
614 | 0 | else |
615 | 0 | { |
616 | 0 | ereport(FATAL, |
617 | 0 | errmsg("could not locate required checkpoint record at %X/%08X", |
618 | 0 | LSN_FORMAT_ARGS(CheckPointLoc)), |
619 | 0 | errhint("If you are restoring from a backup, touch \"%s/recovery.signal\" or \"%s/standby.signal\" and add required recovery options.\n" |
620 | 0 | "If you are not restoring from a backup, try removing the file \"%s/backup_label\".\n" |
621 | 0 | "Be careful: removing \"%s/backup_label\" will result in a corrupt cluster if restoring from a backup.", |
622 | 0 | DataDir, DataDir, DataDir, DataDir)); |
623 | 0 | wasShutdown = false; /* keep compiler quiet */ |
624 | 0 | } |
625 | | |
626 | | /* Read the tablespace_map file if present and create symlinks. */ |
627 | 0 | if (read_tablespace_map(&tablespaces)) |
628 | 0 | { |
629 | 0 | ListCell *lc; |
630 | |
|
631 | 0 | foreach(lc, tablespaces) |
632 | 0 | { |
633 | 0 | tablespaceinfo *ti = lfirst(lc); |
634 | 0 | char *linkloc; |
635 | |
|
636 | 0 | linkloc = psprintf("%s/%u", PG_TBLSPC_DIR, ti->oid); |
637 | | |
638 | | /* |
639 | | * Remove the existing symlink if any and Create the symlink |
640 | | * under PGDATA. |
641 | | */ |
642 | 0 | remove_tablespace_symlink(linkloc); |
643 | |
|
644 | 0 | if (symlink(ti->path, linkloc) < 0) |
645 | 0 | ereport(ERROR, |
646 | 0 | (errcode_for_file_access(), |
647 | 0 | errmsg("could not create symbolic link \"%s\": %m", |
648 | 0 | linkloc))); |
649 | | |
650 | 0 | pfree(ti->path); |
651 | 0 | pfree(ti); |
652 | 0 | } |
653 | | |
654 | | /* tell the caller to delete it later */ |
655 | 0 | haveTblspcMap = true; |
656 | 0 | } |
657 | | |
658 | | /* tell the caller to delete it later */ |
659 | 0 | haveBackupLabel = true; |
660 | 0 | } |
661 | 0 | else |
662 | 0 | { |
663 | | /* No backup_label file has been found if we are here. */ |
664 | | |
665 | | /* |
666 | | * If tablespace_map file is present without backup_label file, there |
667 | | * is no use of such file. There is no harm in retaining it, but it |
668 | | * is better to get rid of the map file so that we don't have any |
669 | | * redundant file in data directory and it will avoid any sort of |
670 | | * confusion. It seems prudent though to just rename the file out of |
671 | | * the way rather than delete it completely, also we ignore any error |
672 | | * that occurs in rename operation as even if map file is present |
673 | | * without backup_label file, it is harmless. |
674 | | */ |
675 | 0 | if (stat(TABLESPACE_MAP, &st) == 0) |
676 | 0 | { |
677 | 0 | unlink(TABLESPACE_MAP_OLD); |
678 | 0 | if (durable_rename(TABLESPACE_MAP, TABLESPACE_MAP_OLD, DEBUG1) == 0) |
679 | 0 | ereport(LOG, |
680 | 0 | (errmsg("ignoring file \"%s\" because no file \"%s\" exists", |
681 | 0 | TABLESPACE_MAP, BACKUP_LABEL_FILE), |
682 | 0 | errdetail("File \"%s\" was renamed to \"%s\".", |
683 | 0 | TABLESPACE_MAP, TABLESPACE_MAP_OLD))); |
684 | 0 | else |
685 | 0 | ereport(LOG, |
686 | 0 | (errmsg("ignoring file \"%s\" because no file \"%s\" exists", |
687 | 0 | TABLESPACE_MAP, BACKUP_LABEL_FILE), |
688 | 0 | errdetail("Could not rename file \"%s\" to \"%s\": %m.", |
689 | 0 | TABLESPACE_MAP, TABLESPACE_MAP_OLD))); |
690 | 0 | } |
691 | | |
692 | | /* |
693 | | * It's possible that archive recovery was requested, but we don't |
694 | | * know how far we need to replay the WAL before we reach consistency. |
695 | | * This can happen for example if a base backup is taken from a |
696 | | * running server using an atomic filesystem snapshot, without calling |
697 | | * pg_backup_start/stop. Or if you just kill a running primary server |
698 | | * and put it into archive recovery by creating a recovery signal |
699 | | * file. |
700 | | * |
701 | | * Our strategy in that case is to perform crash recovery first, |
702 | | * replaying all the WAL present in pg_wal, and only enter archive |
703 | | * recovery after that. |
704 | | * |
705 | | * But usually we already know how far we need to replay the WAL (up |
706 | | * to minRecoveryPoint, up to backupEndPoint, or until we see an |
707 | | * end-of-backup record), and we can enter archive recovery directly. |
708 | | */ |
709 | 0 | if (ArchiveRecoveryRequested && |
710 | 0 | (XLogRecPtrIsValid(ControlFile->minRecoveryPoint) || |
711 | 0 | ControlFile->backupEndRequired || |
712 | 0 | XLogRecPtrIsValid(ControlFile->backupEndPoint) || |
713 | 0 | ControlFile->state == DB_SHUTDOWNED)) |
714 | 0 | { |
715 | 0 | InArchiveRecovery = true; |
716 | 0 | if (StandbyModeRequested) |
717 | 0 | EnableStandbyMode(); |
718 | 0 | } |
719 | | |
720 | | /* |
721 | | * For the same reason as when starting up with backup_label present, |
722 | | * emit a log message when we continue initializing from a base |
723 | | * backup. |
724 | | */ |
725 | 0 | if (XLogRecPtrIsValid(ControlFile->backupStartPoint)) |
726 | 0 | ereport(LOG, |
727 | 0 | errmsg("restarting backup recovery with redo LSN %X/%08X", |
728 | 0 | LSN_FORMAT_ARGS(ControlFile->backupStartPoint))); |
729 | | |
730 | | /* Get the last valid checkpoint record. */ |
731 | 0 | CheckPointLoc = ControlFile->checkPoint; |
732 | 0 | CheckPointTLI = ControlFile->checkPointCopy.ThisTimeLineID; |
733 | 0 | RedoStartLSN = ControlFile->checkPointCopy.redo; |
734 | 0 | RedoStartTLI = ControlFile->checkPointCopy.ThisTimeLineID; |
735 | 0 | record = ReadCheckpointRecord(xlogprefetcher, CheckPointLoc, |
736 | 0 | CheckPointTLI); |
737 | 0 | if (record != NULL) |
738 | 0 | { |
739 | 0 | ereport(DEBUG1, |
740 | 0 | errmsg_internal("checkpoint record is at %X/%08X", |
741 | 0 | LSN_FORMAT_ARGS(CheckPointLoc))); |
742 | 0 | } |
743 | 0 | else |
744 | 0 | { |
745 | | /* |
746 | | * We used to attempt to go back to a secondary checkpoint record |
747 | | * here, but only when not in standby mode. We now just fail if we |
748 | | * can't read the last checkpoint because this allows us to |
749 | | * simplify processing around checkpoints. |
750 | | */ |
751 | 0 | ereport(FATAL, |
752 | 0 | errmsg("could not locate a valid checkpoint record at %X/%08X", |
753 | 0 | LSN_FORMAT_ARGS(CheckPointLoc))); |
754 | 0 | } |
755 | 0 | memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint)); |
756 | 0 | wasShutdown = ((record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN); |
757 | | |
758 | | /* Make sure that REDO location exists. */ |
759 | 0 | if (checkPoint.redo < CheckPointLoc) |
760 | 0 | { |
761 | 0 | XLogPrefetcherBeginRead(xlogprefetcher, checkPoint.redo); |
762 | 0 | if (!ReadRecord(xlogprefetcher, LOG, false, checkPoint.ThisTimeLineID)) |
763 | 0 | ereport(FATAL, |
764 | 0 | errmsg("could not find redo location %X/%08X referenced by checkpoint record at %X/%08X", |
765 | 0 | LSN_FORMAT_ARGS(checkPoint.redo), LSN_FORMAT_ARGS(CheckPointLoc))); |
766 | 0 | } |
767 | 0 | } |
768 | | |
769 | 0 | if (ArchiveRecoveryRequested) |
770 | 0 | { |
771 | 0 | if (StandbyModeRequested) |
772 | 0 | ereport(LOG, |
773 | 0 | (errmsg("entering standby mode"))); |
774 | 0 | else if (recoveryTarget == RECOVERY_TARGET_XID) |
775 | 0 | ereport(LOG, |
776 | 0 | (errmsg("starting point-in-time recovery to XID %u", |
777 | 0 | recoveryTargetXid))); |
778 | 0 | else if (recoveryTarget == RECOVERY_TARGET_TIME) |
779 | 0 | ereport(LOG, |
780 | 0 | (errmsg("starting point-in-time recovery to %s", |
781 | 0 | timestamptz_to_str(recoveryTargetTime)))); |
782 | 0 | else if (recoveryTarget == RECOVERY_TARGET_NAME) |
783 | 0 | ereport(LOG, |
784 | 0 | (errmsg("starting point-in-time recovery to \"%s\"", |
785 | 0 | recoveryTargetName))); |
786 | 0 | else if (recoveryTarget == RECOVERY_TARGET_LSN) |
787 | 0 | ereport(LOG, |
788 | 0 | errmsg("starting point-in-time recovery to WAL location (LSN) \"%X/%08X\"", |
789 | 0 | LSN_FORMAT_ARGS(recoveryTargetLSN))); |
790 | 0 | else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE) |
791 | 0 | ereport(LOG, |
792 | 0 | (errmsg("starting point-in-time recovery to earliest consistent point"))); |
793 | 0 | else |
794 | 0 | ereport(LOG, |
795 | 0 | (errmsg("starting archive recovery"))); |
796 | 0 | } |
797 | | |
798 | | /* |
799 | | * If the location of the checkpoint record is not on the expected |
800 | | * timeline in the history of the requested timeline, we cannot proceed: |
801 | | * the backup is not part of the history of the requested timeline. |
802 | | */ |
803 | 0 | Assert(expectedTLEs); /* was initialized by reading checkpoint |
804 | | * record */ |
805 | 0 | if (tliOfPointInHistory(CheckPointLoc, expectedTLEs) != |
806 | 0 | CheckPointTLI) |
807 | 0 | { |
808 | 0 | XLogRecPtr switchpoint; |
809 | | |
810 | | /* |
811 | | * tliSwitchPoint will throw an error if the checkpoint's timeline is |
812 | | * not in expectedTLEs at all. |
813 | | */ |
814 | 0 | switchpoint = tliSwitchPoint(CheckPointTLI, expectedTLEs, NULL); |
815 | 0 | ereport(FATAL, |
816 | 0 | (errmsg("requested timeline %u is not a child of this server's history", |
817 | 0 | recoveryTargetTLI), |
818 | | /* translator: %s is a backup_label file or a pg_control file */ |
819 | 0 | errdetail("Latest checkpoint in file \"%s\" is at %X/%08X on timeline %u, but in the history of the requested timeline, the server forked off from that timeline at %X/%08X.", |
820 | 0 | haveBackupLabel ? "backup_label" : "pg_control", |
821 | 0 | LSN_FORMAT_ARGS(CheckPointLoc), |
822 | 0 | CheckPointTLI, |
823 | 0 | LSN_FORMAT_ARGS(switchpoint)))); |
824 | 0 | } |
825 | | |
826 | | /* |
827 | | * The min recovery point should be part of the requested timeline's |
828 | | * history, too. |
829 | | */ |
830 | 0 | if (XLogRecPtrIsValid(ControlFile->minRecoveryPoint) && |
831 | 0 | tliOfPointInHistory(ControlFile->minRecoveryPoint - 1, expectedTLEs) != |
832 | 0 | ControlFile->minRecoveryPointTLI) |
833 | 0 | ereport(FATAL, |
834 | 0 | errmsg("requested timeline %u does not contain minimum recovery point %X/%08X on timeline %u", |
835 | 0 | recoveryTargetTLI, |
836 | 0 | LSN_FORMAT_ARGS(ControlFile->minRecoveryPoint), |
837 | 0 | ControlFile->minRecoveryPointTLI)); |
838 | | |
839 | 0 | ereport(DEBUG1, |
840 | 0 | errmsg_internal("redo record is at %X/%08X; shutdown %s", |
841 | 0 | LSN_FORMAT_ARGS(checkPoint.redo), |
842 | 0 | wasShutdown ? "true" : "false")); |
843 | 0 | ereport(DEBUG1, |
844 | 0 | (errmsg_internal("next transaction ID: " UINT64_FORMAT "; next OID: %" PRIu64, |
845 | 0 | U64FromFullTransactionId(checkPoint.nextXid), |
846 | 0 | checkPoint.nextOid))); |
847 | 0 | ereport(DEBUG1, |
848 | 0 | (errmsg_internal("next MultiXactId: %u; next MultiXactOffset: %" PRIu64, |
849 | 0 | checkPoint.nextMulti, checkPoint.nextMultiOffset))); |
850 | 0 | ereport(DEBUG1, |
851 | 0 | (errmsg_internal("oldest unfrozen transaction ID: %u, in database %u", |
852 | 0 | checkPoint.oldestXid, checkPoint.oldestXidDB))); |
853 | 0 | ereport(DEBUG1, |
854 | 0 | (errmsg_internal("oldest MultiXactId: %u, in database %u", |
855 | 0 | checkPoint.oldestMulti, checkPoint.oldestMultiDB))); |
856 | 0 | ereport(DEBUG1, |
857 | 0 | (errmsg_internal("commit timestamp Xid oldest/newest: %u/%u", |
858 | 0 | checkPoint.oldestCommitTsXid, |
859 | 0 | checkPoint.newestCommitTsXid))); |
860 | 0 | if (!TransactionIdIsNormal(XidFromFullTransactionId(checkPoint.nextXid))) |
861 | 0 | ereport(PANIC, |
862 | 0 | (errmsg("invalid next transaction ID"))); |
863 | | |
864 | | /* sanity check */ |
865 | 0 | if (checkPoint.redo > CheckPointLoc) |
866 | 0 | ereport(PANIC, |
867 | 0 | (errmsg("invalid redo in checkpoint record"))); |
868 | | |
869 | | /* |
870 | | * Check whether we need to force recovery from WAL. If it appears to |
871 | | * have been a clean shutdown and we did not have a recovery signal file, |
872 | | * then assume no recovery needed. |
873 | | */ |
874 | 0 | if (checkPoint.redo < CheckPointLoc) |
875 | 0 | { |
876 | 0 | if (wasShutdown) |
877 | 0 | ereport(PANIC, |
878 | 0 | (errmsg("invalid redo record in shutdown checkpoint"))); |
879 | 0 | InRecovery = true; |
880 | 0 | } |
881 | 0 | else if (ControlFile->state != DB_SHUTDOWNED) |
882 | 0 | InRecovery = true; |
883 | 0 | else if (ArchiveRecoveryRequested) |
884 | 0 | { |
885 | | /* force recovery due to presence of recovery signal file */ |
886 | 0 | InRecovery = true; |
887 | 0 | } |
888 | | |
889 | | /* |
890 | | * If recovery is needed, update our in-memory copy of pg_control to show |
891 | | * that we are recovering and to show the selected checkpoint as the place |
892 | | * we are starting from. We also mark pg_control with any minimum recovery |
893 | | * stop point obtained from a backup history file. |
894 | | * |
895 | | * We don't write the changes to disk yet, though. Only do that after |
896 | | * initializing various subsystems. |
897 | | */ |
898 | 0 | if (InRecovery) |
899 | 0 | { |
900 | 0 | if (InArchiveRecovery) |
901 | 0 | { |
902 | 0 | ControlFile->state = DB_IN_ARCHIVE_RECOVERY; |
903 | 0 | } |
904 | 0 | else |
905 | 0 | { |
906 | 0 | ereport(LOG, |
907 | 0 | (errmsg("database system was not properly shut down; " |
908 | 0 | "automatic recovery in progress"))); |
909 | 0 | if (recoveryTargetTLI > ControlFile->checkPointCopy.ThisTimeLineID) |
910 | 0 | ereport(LOG, |
911 | 0 | (errmsg("crash recovery starts in timeline %u " |
912 | 0 | "and has target timeline %u", |
913 | 0 | ControlFile->checkPointCopy.ThisTimeLineID, |
914 | 0 | recoveryTargetTLI))); |
915 | 0 | ControlFile->state = DB_IN_CRASH_RECOVERY; |
916 | 0 | } |
917 | 0 | ControlFile->checkPoint = CheckPointLoc; |
918 | 0 | ControlFile->checkPointCopy = checkPoint; |
919 | 0 | if (InArchiveRecovery) |
920 | 0 | { |
921 | | /* initialize minRecoveryPoint if not set yet */ |
922 | 0 | if (ControlFile->minRecoveryPoint < checkPoint.redo) |
923 | 0 | { |
924 | 0 | ControlFile->minRecoveryPoint = checkPoint.redo; |
925 | 0 | ControlFile->minRecoveryPointTLI = checkPoint.ThisTimeLineID; |
926 | 0 | } |
927 | 0 | } |
928 | | |
929 | | /* |
930 | | * Set backupStartPoint if we're starting recovery from a base backup. |
931 | | * |
932 | | * Also set backupEndPoint and use minRecoveryPoint as the backup end |
933 | | * location if we're starting recovery from a base backup which was |
934 | | * taken from a standby. In this case, the database system status in |
935 | | * pg_control must indicate that the database was already in recovery. |
936 | | * Usually that will be DB_IN_ARCHIVE_RECOVERY but also can be |
937 | | * DB_SHUTDOWNED_IN_RECOVERY if recovery previously was interrupted |
938 | | * before reaching this point; e.g. because restore_command or |
939 | | * primary_conninfo were faulty. |
940 | | * |
941 | | * Any other state indicates that the backup somehow became corrupted |
942 | | * and we can't sensibly continue with recovery. |
943 | | */ |
944 | 0 | if (haveBackupLabel) |
945 | 0 | { |
946 | 0 | ControlFile->backupStartPoint = checkPoint.redo; |
947 | 0 | ControlFile->backupEndRequired = backupEndRequired; |
948 | |
|
949 | 0 | if (backupFromStandby) |
950 | 0 | { |
951 | 0 | if (dbstate_at_startup != DB_IN_ARCHIVE_RECOVERY && |
952 | 0 | dbstate_at_startup != DB_SHUTDOWNED_IN_RECOVERY) |
953 | 0 | ereport(FATAL, |
954 | 0 | (errmsg("backup_label contains data inconsistent with control file"), |
955 | 0 | errhint("This means that the backup is corrupted and you will " |
956 | 0 | "have to use another backup for recovery."))); |
957 | 0 | ControlFile->backupEndPoint = ControlFile->minRecoveryPoint; |
958 | 0 | } |
959 | 0 | } |
960 | 0 | } |
961 | | |
962 | | /* remember these, so that we know when we have reached consistency */ |
963 | 0 | backupStartPoint = ControlFile->backupStartPoint; |
964 | 0 | backupEndRequired = ControlFile->backupEndRequired; |
965 | 0 | backupEndPoint = ControlFile->backupEndPoint; |
966 | 0 | if (InArchiveRecovery) |
967 | 0 | { |
968 | 0 | minRecoveryPoint = ControlFile->minRecoveryPoint; |
969 | 0 | minRecoveryPointTLI = ControlFile->minRecoveryPointTLI; |
970 | 0 | } |
971 | 0 | else |
972 | 0 | { |
973 | 0 | minRecoveryPoint = InvalidXLogRecPtr; |
974 | 0 | minRecoveryPointTLI = 0; |
975 | 0 | } |
976 | | |
977 | | /* |
978 | | * Start recovery assuming that the final record isn't lost. |
979 | | */ |
980 | 0 | abortedRecPtr = InvalidXLogRecPtr; |
981 | 0 | missingContrecPtr = InvalidXLogRecPtr; |
982 | |
|
983 | 0 | *wasShutdown_ptr = wasShutdown; |
984 | 0 | *haveBackupLabel_ptr = haveBackupLabel; |
985 | 0 | *haveTblspcMap_ptr = haveTblspcMap; |
986 | 0 | } |
987 | | |
988 | | /* |
989 | | * See if there are any recovery signal files and if so, set state for |
990 | | * recovery. |
991 | | * |
992 | | * See if there is a recovery command file (recovery.conf), and if so |
993 | | * throw an ERROR since as of PG12 we no longer recognize that. |
994 | | */ |
995 | | static void |
996 | | readRecoverySignalFile(void) |
997 | 0 | { |
998 | 0 | struct stat stat_buf; |
999 | |
|
1000 | 0 | if (IsBootstrapProcessingMode()) |
1001 | 0 | return; |
1002 | | |
1003 | | /* |
1004 | | * Check for old recovery API file: recovery.conf |
1005 | | */ |
1006 | 0 | if (stat(RECOVERY_COMMAND_FILE, &stat_buf) == 0) |
1007 | 0 | ereport(FATAL, |
1008 | 0 | (errcode_for_file_access(), |
1009 | 0 | errmsg("using recovery command file \"%s\" is not supported", |
1010 | 0 | RECOVERY_COMMAND_FILE))); |
1011 | | |
1012 | | /* |
1013 | | * Remove unused .done file, if present. Ignore if absent. |
1014 | | */ |
1015 | 0 | unlink(RECOVERY_COMMAND_DONE); |
1016 | | |
1017 | | /* |
1018 | | * Check for recovery signal files and if found, fsync them since they |
1019 | | * represent server state information. We don't sweat too much about the |
1020 | | * possibility of fsync failure, however. |
1021 | | */ |
1022 | 0 | if (stat(STANDBY_SIGNAL_FILE, &stat_buf) == 0) |
1023 | 0 | { |
1024 | 0 | int fd; |
1025 | |
|
1026 | 0 | fd = BasicOpenFilePerm(STANDBY_SIGNAL_FILE, O_RDWR | PG_BINARY, |
1027 | 0 | S_IRUSR | S_IWUSR); |
1028 | 0 | if (fd >= 0) |
1029 | 0 | { |
1030 | 0 | (void) pg_fsync(fd); |
1031 | 0 | close(fd); |
1032 | 0 | } |
1033 | 0 | standby_signal_file_found = true; |
1034 | 0 | } |
1035 | |
|
1036 | 0 | if (stat(RECOVERY_SIGNAL_FILE, &stat_buf) == 0) |
1037 | 0 | { |
1038 | 0 | int fd; |
1039 | |
|
1040 | 0 | fd = BasicOpenFilePerm(RECOVERY_SIGNAL_FILE, O_RDWR | PG_BINARY, |
1041 | 0 | S_IRUSR | S_IWUSR); |
1042 | 0 | if (fd >= 0) |
1043 | 0 | { |
1044 | 0 | (void) pg_fsync(fd); |
1045 | 0 | close(fd); |
1046 | 0 | } |
1047 | 0 | recovery_signal_file_found = true; |
1048 | 0 | } |
1049 | | |
1050 | | /* |
1051 | | * If both signal files are present, standby signal file takes precedence. |
1052 | | * If neither is present then we won't enter archive recovery. |
1053 | | */ |
1054 | 0 | StandbyModeRequested = false; |
1055 | 0 | ArchiveRecoveryRequested = false; |
1056 | 0 | if (standby_signal_file_found) |
1057 | 0 | { |
1058 | 0 | StandbyModeRequested = true; |
1059 | 0 | ArchiveRecoveryRequested = true; |
1060 | 0 | } |
1061 | 0 | else if (recovery_signal_file_found) |
1062 | 0 | { |
1063 | 0 | StandbyModeRequested = false; |
1064 | 0 | ArchiveRecoveryRequested = true; |
1065 | 0 | } |
1066 | 0 | else |
1067 | 0 | return; |
1068 | | |
1069 | | /* |
1070 | | * We don't support standby mode in standalone backends; that requires |
1071 | | * other processes such as the WAL receiver to be alive. |
1072 | | */ |
1073 | 0 | if (StandbyModeRequested && !IsUnderPostmaster) |
1074 | 0 | ereport(FATAL, |
1075 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
1076 | 0 | errmsg("standby mode is not supported by single-user servers"))); |
1077 | 0 | } |
1078 | | |
1079 | | static void |
1080 | | validateRecoveryParameters(void) |
1081 | 0 | { |
1082 | | /* Reject conflicting targets even when recovery was not requested */ |
1083 | 0 | recoveryTarget = DetermineRecoveryTargetType(); |
1084 | |
|
1085 | 0 | if (!ArchiveRecoveryRequested) |
1086 | 0 | return; |
1087 | | |
1088 | | /* |
1089 | | * Check for compulsory parameters |
1090 | | */ |
1091 | 0 | if (StandbyModeRequested) |
1092 | 0 | { |
1093 | 0 | if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) && |
1094 | 0 | (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)) |
1095 | 0 | ereport(WARNING, |
1096 | 0 | (errmsg("specified neither \"primary_conninfo\" nor \"restore_command\""), |
1097 | 0 | errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there."))); |
1098 | 0 | } |
1099 | 0 | else |
1100 | 0 | { |
1101 | 0 | if (recoveryRestoreCommand == NULL || |
1102 | 0 | strcmp(recoveryRestoreCommand, "") == 0) |
1103 | 0 | ereport(FATAL, |
1104 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1105 | 0 | errmsg("must specify \"restore_command\" when standby mode is not enabled"))); |
1106 | 0 | } |
1107 | | |
1108 | | /* |
1109 | | * Override any inconsistent requests. Note that this is a change of |
1110 | | * behaviour in 9.5; prior to this we simply ignored a request to pause if |
1111 | | * hot_standby = off, which was surprising behaviour. |
1112 | | */ |
1113 | 0 | if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE && |
1114 | 0 | !EnableHotStandby) |
1115 | 0 | recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN; |
1116 | | |
1117 | | /* |
1118 | | * Final parsing of recovery_target_time string; see also |
1119 | | * check_recovery_target_time(). |
1120 | | */ |
1121 | 0 | if (recoveryTarget == RECOVERY_TARGET_TIME) |
1122 | 0 | { |
1123 | 0 | recoveryTargetTime = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in, |
1124 | 0 | CStringGetDatum(recovery_target_time_string), |
1125 | 0 | ObjectIdGetDatum(InvalidOid), |
1126 | 0 | Int32GetDatum(-1))); |
1127 | 0 | } |
1128 | | |
1129 | | /* |
1130 | | * If user specified recovery_target_timeline, validate it or compute the |
1131 | | * "latest" value. We can't do this until after we've gotten the restore |
1132 | | * command and set InArchiveRecovery, because we need to fetch timeline |
1133 | | * history files from the archive. |
1134 | | */ |
1135 | 0 | if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) |
1136 | 0 | { |
1137 | 0 | TimeLineID rtli = recoveryTargetTLIRequested; |
1138 | | |
1139 | | /* Timeline 1 does not have a history file, all else should */ |
1140 | 0 | if (rtli != 1 && !existsTimeLineHistory(rtli)) |
1141 | 0 | ereport(FATAL, |
1142 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
1143 | 0 | errmsg("recovery target timeline %u does not exist", |
1144 | 0 | rtli))); |
1145 | 0 | recoveryTargetTLI = rtli; |
1146 | 0 | } |
1147 | 0 | else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) |
1148 | 0 | { |
1149 | | /* We start the "latest" search from pg_control's timeline */ |
1150 | 0 | recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI); |
1151 | 0 | } |
1152 | 0 | else |
1153 | 0 | { |
1154 | | /* |
1155 | | * else we just use the recoveryTargetTLI as already read from |
1156 | | * ControlFile |
1157 | | */ |
1158 | 0 | Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE); |
1159 | 0 | } |
1160 | 0 | } |
1161 | | |
1162 | | /* |
1163 | | * read_backup_label: check to see if a backup_label file is present |
1164 | | * |
1165 | | * If we see a backup_label during recovery, we assume that we are recovering |
1166 | | * from a backup dump file, and we therefore roll forward from the checkpoint |
1167 | | * identified by the label file, NOT what pg_control says. This avoids the |
1168 | | * problem that pg_control might have been archived one or more checkpoints |
1169 | | * later than the start of the dump, and so if we rely on it as the start |
1170 | | * point, we will fail to restore a consistent database state. |
1171 | | * |
1172 | | * Returns true if a backup_label was found (and fills the checkpoint |
1173 | | * location and TLI into *checkPointLoc and *backupLabelTLI, respectively); |
1174 | | * returns false if not. If this backup_label came from a streamed backup, |
1175 | | * *backupEndRequired is set to true. If this backup_label was created during |
1176 | | * recovery, *backupFromStandby is set to true. |
1177 | | * |
1178 | | * Also sets the global variables RedoStartLSN and RedoStartTLI with the LSN |
1179 | | * and TLI read from the backup file. |
1180 | | */ |
1181 | | static bool |
1182 | | read_backup_label(XLogRecPtr *checkPointLoc, TimeLineID *backupLabelTLI, |
1183 | | bool *backupEndRequired, bool *backupFromStandby) |
1184 | 0 | { |
1185 | 0 | char startxlogfilename[MAXFNAMELEN]; |
1186 | 0 | TimeLineID tli_from_walseg, |
1187 | 0 | tli_from_file; |
1188 | 0 | FILE *lfp; |
1189 | 0 | char ch; |
1190 | 0 | char backuptype[20]; |
1191 | 0 | char backupfrom[20]; |
1192 | 0 | char backuplabel[MAXPGPATH]; |
1193 | 0 | char backuptime[128]; |
1194 | 0 | char lsn[MAXPG_LSNLEN + 1]; |
1195 | | |
1196 | | /* suppress possible uninitialized-variable warnings */ |
1197 | 0 | *checkPointLoc = InvalidXLogRecPtr; |
1198 | 0 | *backupLabelTLI = 0; |
1199 | 0 | *backupEndRequired = false; |
1200 | 0 | *backupFromStandby = false; |
1201 | | |
1202 | | /* |
1203 | | * See if label file is present |
1204 | | */ |
1205 | 0 | lfp = AllocateFile(BACKUP_LABEL_FILE, "r"); |
1206 | 0 | if (!lfp) |
1207 | 0 | { |
1208 | 0 | if (errno != ENOENT) |
1209 | 0 | ereport(FATAL, |
1210 | 0 | (errcode_for_file_access(), |
1211 | 0 | errmsg("could not read file \"%s\": %m", |
1212 | 0 | BACKUP_LABEL_FILE))); |
1213 | 0 | return false; /* it's not there, all is fine */ |
1214 | 0 | } |
1215 | | |
1216 | | /* |
1217 | | * Read and parse the START WAL LOCATION and CHECKPOINT lines (this code |
1218 | | * is pretty crude, but we are not expecting any variability in the file |
1219 | | * format). |
1220 | | */ |
1221 | 0 | if (fscanf(lfp, |
1222 | 0 | "START WAL LOCATION: %" CppAsString2(MAXPG_LSNLEN) "[0123456789ABCDEFabcdef/] (file %08X%16s)%c", |
1223 | 0 | lsn, &tli_from_walseg, startxlogfilename, &ch) != 4 || |
1224 | 0 | ch != '\n' || |
1225 | 0 | !pg_parse_lsn(lsn, &RedoStartLSN)) |
1226 | 0 | ereport(FATAL, |
1227 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1228 | 0 | errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE))); |
1229 | 0 | RedoStartTLI = tli_from_walseg; |
1230 | 0 | if (fscanf(lfp, |
1231 | 0 | "CHECKPOINT LOCATION: %" CppAsString2(MAXPG_LSNLEN) "[0123456789ABCDEFabcdef/]%c", lsn, |
1232 | 0 | &ch) != 2 || |
1233 | 0 | ch != '\n' || |
1234 | 0 | !pg_parse_lsn(lsn, checkPointLoc)) |
1235 | 0 | ereport(FATAL, |
1236 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1237 | 0 | errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE))); |
1238 | 0 | *backupLabelTLI = tli_from_walseg; |
1239 | | |
1240 | | /* |
1241 | | * BACKUP METHOD lets us know if this was a typical backup ("streamed", |
1242 | | * which could mean either pg_basebackup or the pg_backup_start/stop |
1243 | | * method was used) or if this label came from somewhere else (the only |
1244 | | * other option today being from pg_rewind). If this was a streamed |
1245 | | * backup then we know that we need to play through until we get to the |
1246 | | * end of the WAL which was generated during the backup (at which point we |
1247 | | * will have reached consistency and backupEndRequired will be reset to be |
1248 | | * false). |
1249 | | */ |
1250 | 0 | if (fscanf(lfp, "BACKUP METHOD: %19s\n", backuptype) == 1) |
1251 | 0 | { |
1252 | 0 | if (strcmp(backuptype, "streamed") == 0) |
1253 | 0 | *backupEndRequired = true; |
1254 | 0 | } |
1255 | | |
1256 | | /* |
1257 | | * BACKUP FROM lets us know if this was from a primary or a standby. If |
1258 | | * it was from a standby, we'll double-check that the control file state |
1259 | | * matches that of a standby. |
1260 | | */ |
1261 | 0 | if (fscanf(lfp, "BACKUP FROM: %19s\n", backupfrom) == 1) |
1262 | 0 | { |
1263 | 0 | if (strcmp(backupfrom, "standby") == 0) |
1264 | 0 | *backupFromStandby = true; |
1265 | 0 | } |
1266 | | |
1267 | | /* |
1268 | | * Parse START TIME and LABEL. Those are not mandatory fields for recovery |
1269 | | * but checking for their presence is useful for debugging and the next |
1270 | | * sanity checks. Cope also with the fact that the result buffers have a |
1271 | | * pre-allocated size, hence if the backup_label file has been generated |
1272 | | * with strings longer than the maximum assumed here an incorrect parsing |
1273 | | * happens. That's fine as only minor consistency checks are done |
1274 | | * afterwards. |
1275 | | */ |
1276 | 0 | if (fscanf(lfp, "START TIME: %127[^\n]\n", backuptime) == 1) |
1277 | 0 | ereport(DEBUG1, |
1278 | 0 | (errmsg_internal("backup time %s in file \"%s\"", |
1279 | 0 | backuptime, BACKUP_LABEL_FILE))); |
1280 | | |
1281 | 0 | if (fscanf(lfp, "LABEL: %1023[^\n]\n", backuplabel) == 1) |
1282 | 0 | ereport(DEBUG1, |
1283 | 0 | (errmsg_internal("backup label %s in file \"%s\"", |
1284 | 0 | backuplabel, BACKUP_LABEL_FILE))); |
1285 | | |
1286 | | /* |
1287 | | * START TIMELINE is new as of 11. Its parsing is not mandatory, still use |
1288 | | * it as a sanity check if present. |
1289 | | */ |
1290 | 0 | if (fscanf(lfp, "START TIMELINE: %u\n", &tli_from_file) == 1) |
1291 | 0 | { |
1292 | 0 | if (tli_from_walseg != tli_from_file) |
1293 | 0 | ereport(FATAL, |
1294 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1295 | 0 | errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE), |
1296 | 0 | errdetail("Timeline ID parsed is %u, but expected %u.", |
1297 | 0 | tli_from_file, tli_from_walseg))); |
1298 | | |
1299 | 0 | ereport(DEBUG1, |
1300 | 0 | (errmsg_internal("backup timeline %u in file \"%s\"", |
1301 | 0 | tli_from_file, BACKUP_LABEL_FILE))); |
1302 | 0 | } |
1303 | | |
1304 | | /* |
1305 | | * INCREMENTAL FROM LSN indicates that this is an incremental backup. The |
1306 | | * LSN itself need not be parsed, so reading its first character is |
1307 | | * sufficient. |
1308 | | */ |
1309 | 0 | if (fscanf(lfp, "INCREMENTAL FROM LSN: %c", &ch) == 1) |
1310 | 0 | ereport(FATAL, |
1311 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1312 | 0 | errmsg("this is an incremental backup, not a data directory"), |
1313 | 0 | errhint("Use pg_combinebackup to reconstruct a valid data directory."))); |
1314 | | |
1315 | 0 | if (ferror(lfp) || FreeFile(lfp)) |
1316 | 0 | ereport(FATAL, |
1317 | 0 | (errcode_for_file_access(), |
1318 | 0 | errmsg("could not read file \"%s\": %m", |
1319 | 0 | BACKUP_LABEL_FILE))); |
1320 | | |
1321 | 0 | return true; |
1322 | 0 | } |
1323 | | |
1324 | | /* |
1325 | | * read_tablespace_map: check to see if a tablespace_map file is present |
1326 | | * |
1327 | | * If we see a tablespace_map file during recovery, we assume that we are |
1328 | | * recovering from a backup dump file, and we therefore need to create symlinks |
1329 | | * as per the information present in tablespace_map file. |
1330 | | * |
1331 | | * Returns true if a tablespace_map file was found (and fills *tablespaces |
1332 | | * with a tablespaceinfo struct for each tablespace listed in the file); |
1333 | | * returns false if not. |
1334 | | */ |
1335 | | static bool |
1336 | | read_tablespace_map(List **tablespaces) |
1337 | 0 | { |
1338 | 0 | tablespaceinfo *ti; |
1339 | 0 | FILE *lfp; |
1340 | 0 | char str[MAXPGPATH]; |
1341 | 0 | int ch, |
1342 | 0 | i, |
1343 | 0 | n; |
1344 | 0 | bool was_backslash; |
1345 | | |
1346 | | /* |
1347 | | * See if tablespace_map file is present |
1348 | | */ |
1349 | 0 | lfp = AllocateFile(TABLESPACE_MAP, "r"); |
1350 | 0 | if (!lfp) |
1351 | 0 | { |
1352 | 0 | if (errno != ENOENT) |
1353 | 0 | ereport(FATAL, |
1354 | 0 | (errcode_for_file_access(), |
1355 | 0 | errmsg("could not read file \"%s\": %m", |
1356 | 0 | TABLESPACE_MAP))); |
1357 | 0 | return false; /* it's not there, all is fine */ |
1358 | 0 | } |
1359 | | |
1360 | | /* |
1361 | | * Read and parse the link name and path lines from tablespace_map file |
1362 | | * (this code is pretty crude, but we are not expecting any variability in |
1363 | | * the file format). De-escape any backslashes that were inserted. |
1364 | | */ |
1365 | 0 | i = 0; |
1366 | 0 | was_backslash = false; |
1367 | 0 | while ((ch = fgetc(lfp)) != EOF) |
1368 | 0 | { |
1369 | 0 | if (!was_backslash && (ch == '\n' || ch == '\r')) |
1370 | 0 | { |
1371 | 0 | char *endp; |
1372 | |
|
1373 | 0 | if (i == 0) |
1374 | 0 | continue; /* \r immediately followed by \n */ |
1375 | | |
1376 | | /* |
1377 | | * The de-escaped line should contain an OID followed by exactly |
1378 | | * one space followed by a path. The path might start with |
1379 | | * spaces, so don't be too liberal about parsing. |
1380 | | */ |
1381 | 0 | str[i] = '\0'; |
1382 | 0 | n = 0; |
1383 | 0 | while (str[n] && str[n] != ' ') |
1384 | 0 | n++; |
1385 | 0 | if (n < 1 || n >= i - 1) |
1386 | 0 | ereport(FATAL, |
1387 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1388 | 0 | errmsg("invalid data in file \"%s\"", TABLESPACE_MAP))); |
1389 | 0 | str[n++] = '\0'; |
1390 | |
|
1391 | 0 | ti = palloc0_object(tablespaceinfo); |
1392 | 0 | errno = 0; |
1393 | 0 | ti->oid = strtoul(str, &endp, 10); |
1394 | 0 | if (*endp != '\0' || errno == EINVAL || errno == ERANGE) |
1395 | 0 | ereport(FATAL, |
1396 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1397 | 0 | errmsg("invalid data in file \"%s\"", TABLESPACE_MAP))); |
1398 | 0 | ti->path = pstrdup(str + n); |
1399 | 0 | *tablespaces = lappend(*tablespaces, ti); |
1400 | |
|
1401 | 0 | i = 0; |
1402 | 0 | continue; |
1403 | 0 | } |
1404 | 0 | else if (!was_backslash && ch == '\\') |
1405 | 0 | was_backslash = true; |
1406 | 0 | else |
1407 | 0 | { |
1408 | 0 | if (i < sizeof(str) - 1) |
1409 | 0 | str[i++] = ch; |
1410 | 0 | was_backslash = false; |
1411 | 0 | } |
1412 | 0 | } |
1413 | | |
1414 | 0 | if (i != 0 || was_backslash) /* last line not terminated? */ |
1415 | 0 | ereport(FATAL, |
1416 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
1417 | 0 | errmsg("invalid data in file \"%s\"", TABLESPACE_MAP))); |
1418 | | |
1419 | 0 | if (ferror(lfp) || FreeFile(lfp)) |
1420 | 0 | ereport(FATAL, |
1421 | 0 | (errcode_for_file_access(), |
1422 | 0 | errmsg("could not read file \"%s\": %m", |
1423 | 0 | TABLESPACE_MAP))); |
1424 | | |
1425 | 0 | return true; |
1426 | 0 | } |
1427 | | |
1428 | | /* |
1429 | | * Finish WAL recovery. |
1430 | | * |
1431 | | * This does not close the 'xlogreader' yet, because in some cases the caller |
1432 | | * still wants to re-read the last checkpoint record by calling |
1433 | | * ReadCheckpointRecord(). |
1434 | | * |
1435 | | * Returns the position of the last valid or applied record, after which new |
1436 | | * WAL should be appended, information about why recovery was ended, and some |
1437 | | * other things. See the EndOfWalRecoveryInfo struct for details. |
1438 | | */ |
1439 | | EndOfWalRecoveryInfo * |
1440 | | FinishWalRecovery(void) |
1441 | 0 | { |
1442 | 0 | EndOfWalRecoveryInfo *result = palloc_object(EndOfWalRecoveryInfo); |
1443 | 0 | XLogRecPtr lastRec; |
1444 | 0 | TimeLineID lastRecTLI; |
1445 | 0 | XLogRecPtr endOfLog; |
1446 | | |
1447 | | /* |
1448 | | * Kill WAL receiver, if it's still running, before we continue to write |
1449 | | * the startup checkpoint and aborted-contrecord records. It will trump |
1450 | | * over these records and subsequent ones if it's still alive when we |
1451 | | * start writing WAL. |
1452 | | */ |
1453 | 0 | XLogShutdownWalRcv(); |
1454 | | |
1455 | | /* |
1456 | | * Shutdown the slot sync worker to drop any temporary slots acquired by |
1457 | | * it and to prevent it from keep trying to fetch the failover slots. |
1458 | | * |
1459 | | * We do not update the 'synced' column in 'pg_replication_slots' system |
1460 | | * view from true to false here, as any failed update could leave 'synced' |
1461 | | * column false for some slots. This could cause issues during slot sync |
1462 | | * after restarting the server as a standby. While updating the 'synced' |
1463 | | * column after switching to the new timeline is an option, it does not |
1464 | | * simplify the handling for the 'synced' column. Therefore, we retain the |
1465 | | * 'synced' column as true after promotion as it may provide useful |
1466 | | * information about the slot origin. |
1467 | | */ |
1468 | 0 | ShutDownSlotSync(); |
1469 | | |
1470 | | /* |
1471 | | * We are now done reading the xlog from stream. Turn off streaming |
1472 | | * recovery to force fetching the files (which would be required at end of |
1473 | | * recovery, e.g., timeline history file) from archive or pg_wal. |
1474 | | * |
1475 | | * Note that standby mode must be turned off after killing WAL receiver, |
1476 | | * i.e., calling XLogShutdownWalRcv(). |
1477 | | */ |
1478 | 0 | Assert(!WalRcvStreaming()); |
1479 | 0 | StandbyMode = false; |
1480 | | |
1481 | | /* |
1482 | | * Determine where to start writing WAL next. |
1483 | | * |
1484 | | * Re-fetch the last valid or last applied record, so we can identify the |
1485 | | * exact endpoint of what we consider the valid portion of WAL. There may |
1486 | | * be an incomplete continuation record after that, in which case |
1487 | | * 'abortedRecPtr' and 'missingContrecPtr' are set and the caller will |
1488 | | * write a special OVERWRITE_CONTRECORD message to mark that the rest of |
1489 | | * it is intentionally missing. See CreateOverwriteContrecordRecord(). |
1490 | | * |
1491 | | * An important side-effect of this is to load the last page into |
1492 | | * xlogreader. The caller uses it to initialize the WAL for writing. |
1493 | | */ |
1494 | 0 | if (!InRecovery) |
1495 | 0 | { |
1496 | 0 | lastRec = CheckPointLoc; |
1497 | 0 | lastRecTLI = CheckPointTLI; |
1498 | 0 | } |
1499 | 0 | else |
1500 | 0 | { |
1501 | 0 | lastRec = XLogRecoveryCtl->lastReplayedReadRecPtr; |
1502 | 0 | lastRecTLI = XLogRecoveryCtl->lastReplayedTLI; |
1503 | 0 | } |
1504 | 0 | XLogPrefetcherBeginRead(xlogprefetcher, lastRec); |
1505 | 0 | (void) ReadRecord(xlogprefetcher, PANIC, false, lastRecTLI); |
1506 | 0 | endOfLog = xlogreader->EndRecPtr; |
1507 | | |
1508 | | /* |
1509 | | * Remember the TLI in the filename of the XLOG segment containing the |
1510 | | * end-of-log. It could be different from the timeline that endOfLog |
1511 | | * nominally belongs to, if there was a timeline switch in that segment, |
1512 | | * and we were reading the old WAL from a segment belonging to a higher |
1513 | | * timeline. |
1514 | | */ |
1515 | 0 | result->endOfLogTLI = xlogreader->seg.ws_tli; |
1516 | |
|
1517 | 0 | if (ArchiveRecoveryRequested) |
1518 | 0 | { |
1519 | | /* |
1520 | | * We are no longer in archive recovery state. |
1521 | | * |
1522 | | * We are now done reading the old WAL. Turn off archive fetching if |
1523 | | * it was active. |
1524 | | */ |
1525 | 0 | Assert(InArchiveRecovery); |
1526 | 0 | InArchiveRecovery = false; |
1527 | | |
1528 | | /* |
1529 | | * If the ending log segment is still open, close it (to avoid |
1530 | | * problems on Windows with trying to rename or delete an open file). |
1531 | | */ |
1532 | 0 | if (readFile >= 0) |
1533 | 0 | { |
1534 | 0 | close(readFile); |
1535 | 0 | readFile = -1; |
1536 | 0 | } |
1537 | 0 | } |
1538 | | |
1539 | | /* |
1540 | | * Copy the last partial block to the caller, for initializing the WAL |
1541 | | * buffer for appending new WAL. |
1542 | | */ |
1543 | 0 | if (endOfLog % XLOG_BLCKSZ != 0) |
1544 | 0 | { |
1545 | 0 | char *page; |
1546 | 0 | int len; |
1547 | 0 | XLogRecPtr pageBeginPtr; |
1548 | |
|
1549 | 0 | pageBeginPtr = endOfLog - (endOfLog % XLOG_BLCKSZ); |
1550 | 0 | Assert(readOff == XLogSegmentOffset(pageBeginPtr, wal_segment_size)); |
1551 | | |
1552 | | /* Copy the valid part of the last block */ |
1553 | 0 | len = endOfLog % XLOG_BLCKSZ; |
1554 | 0 | page = palloc(len); |
1555 | 0 | memcpy(page, xlogreader->readBuf, len); |
1556 | |
|
1557 | 0 | result->lastPageBeginPtr = pageBeginPtr; |
1558 | 0 | result->lastPage = page; |
1559 | 0 | } |
1560 | 0 | else |
1561 | 0 | { |
1562 | | /* There is no partial block to copy. */ |
1563 | 0 | result->lastPageBeginPtr = endOfLog; |
1564 | 0 | result->lastPage = NULL; |
1565 | 0 | } |
1566 | | |
1567 | | /* |
1568 | | * Create a comment for the history file to explain why and where timeline |
1569 | | * changed. |
1570 | | */ |
1571 | 0 | result->recoveryStopReason = getRecoveryStopReason(); |
1572 | |
|
1573 | 0 | result->lastRec = lastRec; |
1574 | 0 | result->lastRecTLI = lastRecTLI; |
1575 | 0 | result->endOfLog = endOfLog; |
1576 | |
|
1577 | 0 | result->abortedRecPtr = abortedRecPtr; |
1578 | 0 | result->missingContrecPtr = missingContrecPtr; |
1579 | |
|
1580 | 0 | result->standby_signal_file_found = standby_signal_file_found; |
1581 | 0 | result->recovery_signal_file_found = recovery_signal_file_found; |
1582 | |
|
1583 | 0 | return result; |
1584 | 0 | } |
1585 | | |
1586 | | /* |
1587 | | * Clean up the WAL reader and leftovers from restoring WAL from archive |
1588 | | */ |
1589 | | void |
1590 | | ShutdownWalRecovery(void) |
1591 | 0 | { |
1592 | 0 | char recoveryPath[MAXPGPATH]; |
1593 | | |
1594 | | /* Final update of pg_stat_recovery_prefetch. */ |
1595 | 0 | XLogPrefetcherComputeStats(xlogprefetcher); |
1596 | | |
1597 | | /* Shut down xlogreader */ |
1598 | 0 | if (readFile >= 0) |
1599 | 0 | { |
1600 | 0 | close(readFile); |
1601 | 0 | readFile = -1; |
1602 | 0 | } |
1603 | 0 | pfree(xlogreader->private_data); |
1604 | 0 | XLogReaderFree(xlogreader); |
1605 | 0 | XLogPrefetcherFree(xlogprefetcher); |
1606 | |
|
1607 | 0 | if (ArchiveRecoveryRequested) |
1608 | 0 | { |
1609 | | /* |
1610 | | * Since there might be a partial WAL segment named RECOVERYXLOG, get |
1611 | | * rid of it. |
1612 | | */ |
1613 | 0 | snprintf(recoveryPath, MAXPGPATH, XLOGDIR "/RECOVERYXLOG"); |
1614 | 0 | unlink(recoveryPath); /* ignore any error */ |
1615 | | |
1616 | | /* Get rid of any remaining recovered timeline-history file, too */ |
1617 | 0 | snprintf(recoveryPath, MAXPGPATH, XLOGDIR "/RECOVERYHISTORY"); |
1618 | 0 | unlink(recoveryPath); /* ignore any error */ |
1619 | 0 | } |
1620 | | |
1621 | | /* |
1622 | | * We don't need the latch anymore. It's not strictly necessary to disown |
1623 | | * it, but let's do it for the sake of tidiness. |
1624 | | */ |
1625 | 0 | if (ArchiveRecoveryRequested) |
1626 | 0 | DisownLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
1627 | 0 | } |
1628 | | |
1629 | | /* |
1630 | | * Perform WAL recovery. |
1631 | | * |
1632 | | * If the system was shut down cleanly, this is never called. |
1633 | | */ |
1634 | | void |
1635 | | PerformWalRecovery(void) |
1636 | 0 | { |
1637 | 0 | XLogRecord *record; |
1638 | 0 | bool reachedRecoveryTarget = false; |
1639 | 0 | TimeLineID replayTLI; |
1640 | | |
1641 | | /* |
1642 | | * Initialize shared variables for tracking progress of WAL replay, as if |
1643 | | * we had just replayed the record before the REDO location (or the |
1644 | | * checkpoint record itself, if it's a shutdown checkpoint). |
1645 | | */ |
1646 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
1647 | 0 | if (RedoStartLSN < CheckPointLoc) |
1648 | 0 | { |
1649 | 0 | XLogRecoveryCtl->lastReplayedReadRecPtr = InvalidXLogRecPtr; |
1650 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr = RedoStartLSN; |
1651 | 0 | XLogRecoveryCtl->lastReplayedTLI = RedoStartTLI; |
1652 | 0 | } |
1653 | 0 | else |
1654 | 0 | { |
1655 | 0 | XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr; |
1656 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr; |
1657 | 0 | XLogRecoveryCtl->lastReplayedTLI = CheckPointTLI; |
1658 | 0 | } |
1659 | 0 | XLogRecoveryCtl->replayEndRecPtr = XLogRecoveryCtl->lastReplayedEndRecPtr; |
1660 | 0 | XLogRecoveryCtl->replayEndTLI = XLogRecoveryCtl->lastReplayedTLI; |
1661 | 0 | XLogRecoveryCtl->recoveryLastXTime = 0; |
1662 | 0 | XLogRecoveryCtl->currentChunkStartTime = 0; |
1663 | 0 | XLogRecoveryCtl->recoveryPauseState = RECOVERY_NOT_PAUSED; |
1664 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
1665 | | |
1666 | | /* Also ensure XLogReceiptTime has a sane value */ |
1667 | 0 | XLogReceiptTime = GetCurrentTimestamp(); |
1668 | | |
1669 | | /* |
1670 | | * Let postmaster know we've started redo now, so that it can launch the |
1671 | | * archiver if necessary. |
1672 | | */ |
1673 | 0 | if (IsUnderPostmaster) |
1674 | 0 | SendPostmasterSignal(PMSIGNAL_RECOVERY_STARTED); |
1675 | | |
1676 | | /* |
1677 | | * Allow read-only connections immediately if we're consistent already. |
1678 | | */ |
1679 | 0 | CheckRecoveryConsistency(); |
1680 | | |
1681 | | /* |
1682 | | * Find the first record that logically follows the checkpoint --- it |
1683 | | * might physically precede it, though. |
1684 | | */ |
1685 | 0 | if (RedoStartLSN < CheckPointLoc) |
1686 | 0 | { |
1687 | | /* back up to find the record */ |
1688 | 0 | replayTLI = RedoStartTLI; |
1689 | 0 | XLogPrefetcherBeginRead(xlogprefetcher, RedoStartLSN); |
1690 | 0 | record = ReadRecord(xlogprefetcher, PANIC, false, replayTLI); |
1691 | | |
1692 | | /* |
1693 | | * If a checkpoint record's redo pointer points back to an earlier |
1694 | | * LSN, the record at that LSN should be an XLOG_CHECKPOINT_REDO |
1695 | | * record. |
1696 | | */ |
1697 | 0 | if (record->xl_rmid != RM_XLOG_ID || |
1698 | 0 | (record->xl_info & ~XLR_INFO_MASK) != XLOG_CHECKPOINT_REDO) |
1699 | 0 | ereport(FATAL, |
1700 | 0 | errmsg("unexpected record type found at redo point %X/%08X", |
1701 | 0 | LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))); |
1702 | 0 | } |
1703 | 0 | else |
1704 | 0 | { |
1705 | | /* just have to read next record after CheckPoint */ |
1706 | 0 | Assert(xlogreader->ReadRecPtr == CheckPointLoc); |
1707 | 0 | replayTLI = CheckPointTLI; |
1708 | 0 | record = ReadRecord(xlogprefetcher, LOG, false, replayTLI); |
1709 | 0 | } |
1710 | | |
1711 | 0 | if (record != NULL) |
1712 | 0 | { |
1713 | 0 | TimestampTz xtime; |
1714 | 0 | PGRUsage ru0; |
1715 | |
|
1716 | 0 | pg_rusage_init(&ru0); |
1717 | |
|
1718 | 0 | InRedo = true; |
1719 | |
|
1720 | 0 | RmgrStartup(); |
1721 | |
|
1722 | 0 | ereport(LOG, |
1723 | 0 | errmsg("redo starts at %X/%08X", |
1724 | 0 | LSN_FORMAT_ARGS(xlogreader->ReadRecPtr))); |
1725 | | |
1726 | | /* Prepare to report progress of the redo phase. */ |
1727 | 0 | if (!StandbyMode) |
1728 | 0 | begin_startup_progress_phase(); |
1729 | | |
1730 | | /* |
1731 | | * main redo apply loop |
1732 | | */ |
1733 | 0 | do |
1734 | 0 | { |
1735 | 0 | if (!StandbyMode) |
1736 | 0 | ereport_startup_progress("redo in progress, elapsed time: %ld.%02d s, current LSN: %X/%08X", |
1737 | 0 | LSN_FORMAT_ARGS(xlogreader->ReadRecPtr)); |
1738 | | |
1739 | | #ifdef WAL_DEBUG |
1740 | | if (XLOG_DEBUG) |
1741 | | { |
1742 | | StringInfoData buf; |
1743 | | |
1744 | | initStringInfo(&buf); |
1745 | | appendStringInfo(&buf, "REDO @ %X/%08X; LSN %X/%08X: ", |
1746 | | LSN_FORMAT_ARGS(xlogreader->ReadRecPtr), |
1747 | | LSN_FORMAT_ARGS(xlogreader->EndRecPtr)); |
1748 | | xlog_outrec(&buf, xlogreader); |
1749 | | appendStringInfoString(&buf, " - "); |
1750 | | xlog_outdesc(&buf, xlogreader); |
1751 | | elog(LOG, "%s", buf.data); |
1752 | | pfree(buf.data); |
1753 | | } |
1754 | | #endif |
1755 | | |
1756 | | /* Handle interrupt signals of startup process */ |
1757 | 0 | ProcessStartupProcInterrupts(); |
1758 | | |
1759 | | /* |
1760 | | * Pause WAL replay, if requested by a hot-standby session via |
1761 | | * SetRecoveryPause(). |
1762 | | * |
1763 | | * Note that we intentionally don't take the info_lck spinlock |
1764 | | * here. We might therefore read a slightly stale value of the |
1765 | | * recoveryPause flag, but it can't be very stale (no worse than |
1766 | | * the last spinlock we did acquire). Since a pause request is a |
1767 | | * pretty asynchronous thing anyway, possibly responding to it one |
1768 | | * WAL record later than we otherwise would is a minor issue, so |
1769 | | * it doesn't seem worth adding another spinlock cycle to prevent |
1770 | | * that. |
1771 | | */ |
1772 | 0 | if (((volatile XLogRecoveryCtlData *) XLogRecoveryCtl)->recoveryPauseState != |
1773 | 0 | RECOVERY_NOT_PAUSED) |
1774 | 0 | recoveryPausesHere(false); |
1775 | | |
1776 | | /* |
1777 | | * Have we reached our recovery target? |
1778 | | */ |
1779 | 0 | if (recoveryStopsBefore(xlogreader)) |
1780 | 0 | { |
1781 | 0 | reachedRecoveryTarget = true; |
1782 | 0 | break; |
1783 | 0 | } |
1784 | | |
1785 | | /* |
1786 | | * If we've been asked to lag the primary, wait on latch until |
1787 | | * enough time has passed. |
1788 | | */ |
1789 | 0 | if (recoveryApplyDelay(xlogreader)) |
1790 | 0 | { |
1791 | | /* |
1792 | | * We test for paused recovery again here. If user sets |
1793 | | * delayed apply, it may be because they expect to pause |
1794 | | * recovery in case of problems, so we must test again here |
1795 | | * otherwise pausing during the delay-wait wouldn't work. |
1796 | | */ |
1797 | 0 | if (((volatile XLogRecoveryCtlData *) XLogRecoveryCtl)->recoveryPauseState != |
1798 | 0 | RECOVERY_NOT_PAUSED) |
1799 | 0 | recoveryPausesHere(false); |
1800 | 0 | } |
1801 | | |
1802 | | /* |
1803 | | * Apply the record |
1804 | | */ |
1805 | 0 | ApplyWalRecord(xlogreader, record, &replayTLI); |
1806 | | |
1807 | | /* |
1808 | | * Wake up processes waiting for standby replay, write, or flush |
1809 | | * LSN to reach current replay position. Replay implies that the |
1810 | | * WAL was already written and flushed to disk, so write and flush |
1811 | | * waiters can be woken at the replay position too. |
1812 | | */ |
1813 | 0 | WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, |
1814 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr); |
1815 | 0 | WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, |
1816 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr); |
1817 | 0 | WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, |
1818 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr); |
1819 | | |
1820 | | /* Exit loop if we reached inclusive recovery target */ |
1821 | 0 | if (recoveryStopsAfter(xlogreader)) |
1822 | 0 | { |
1823 | 0 | reachedRecoveryTarget = true; |
1824 | 0 | break; |
1825 | 0 | } |
1826 | | |
1827 | | /* Else, try to fetch the next WAL record */ |
1828 | 0 | record = ReadRecord(xlogprefetcher, LOG, false, replayTLI); |
1829 | 0 | } while (record != NULL); |
1830 | | |
1831 | | /* |
1832 | | * end of main redo apply loop |
1833 | | */ |
1834 | | |
1835 | 0 | if (reachedRecoveryTarget) |
1836 | 0 | { |
1837 | 0 | if (!reachedConsistency) |
1838 | 0 | ereport(FATAL, |
1839 | 0 | (errmsg("requested recovery stop point is before consistent recovery point"))); |
1840 | | |
1841 | | /* |
1842 | | * This is the last point where we can restart recovery with a new |
1843 | | * recovery target, if we shutdown and begin again. After this, |
1844 | | * Resource Managers may choose to do permanent corrective actions |
1845 | | * at end of recovery. |
1846 | | */ |
1847 | 0 | switch (recoveryTargetAction) |
1848 | 0 | { |
1849 | 0 | case RECOVERY_TARGET_ACTION_SHUTDOWN: |
1850 | | |
1851 | | /* |
1852 | | * exit with special return code to request shutdown of |
1853 | | * postmaster. Log messages issued from postmaster. |
1854 | | */ |
1855 | 0 | proc_exit(3); |
1856 | |
|
1857 | 0 | case RECOVERY_TARGET_ACTION_PAUSE: |
1858 | 0 | SetRecoveryPause(true); |
1859 | 0 | recoveryPausesHere(true); |
1860 | | |
1861 | | /* drop into promote */ |
1862 | 0 | pg_fallthrough; |
1863 | |
|
1864 | 0 | case RECOVERY_TARGET_ACTION_PROMOTE: |
1865 | 0 | break; |
1866 | 0 | } |
1867 | 0 | } |
1868 | | |
1869 | 0 | RmgrCleanup(); |
1870 | |
|
1871 | 0 | ereport(LOG, |
1872 | 0 | errmsg("redo done at %X/%08X system usage: %s", |
1873 | 0 | LSN_FORMAT_ARGS(xlogreader->ReadRecPtr), |
1874 | 0 | pg_rusage_show(&ru0))); |
1875 | 0 | xtime = GetLatestXTime(); |
1876 | 0 | if (xtime) |
1877 | 0 | ereport(LOG, |
1878 | 0 | (errmsg("last completed transaction was at log time %s", |
1879 | 0 | timestamptz_to_str(xtime)))); |
1880 | | |
1881 | 0 | InRedo = false; |
1882 | 0 | } |
1883 | 0 | else |
1884 | 0 | { |
1885 | | /* there are no WAL records following the checkpoint */ |
1886 | 0 | ereport(LOG, |
1887 | 0 | (errmsg("redo is not required"))); |
1888 | 0 | } |
1889 | | |
1890 | | /* |
1891 | | * This check is intentionally after the above log messages that indicate |
1892 | | * how far recovery went. |
1893 | | */ |
1894 | 0 | if (ArchiveRecoveryRequested && |
1895 | 0 | recoveryTarget != RECOVERY_TARGET_UNSET && |
1896 | 0 | !reachedRecoveryTarget) |
1897 | 0 | ereport(FATAL, |
1898 | 0 | (errcode(ERRCODE_CONFIG_FILE_ERROR), |
1899 | 0 | errmsg("recovery ended before configured recovery target was reached"))); |
1900 | 0 | } |
1901 | | |
1902 | | /* |
1903 | | * Subroutine of PerformWalRecovery, to apply one WAL record. |
1904 | | */ |
1905 | | static void |
1906 | | ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *replayTLI) |
1907 | 0 | { |
1908 | 0 | ErrorContextCallback errcallback; |
1909 | 0 | bool switchedTLI = false; |
1910 | | |
1911 | | /* Setup error traceback support for ereport() */ |
1912 | 0 | errcallback.callback = rm_redo_error_callback; |
1913 | 0 | errcallback.arg = xlogreader; |
1914 | 0 | errcallback.previous = error_context_stack; |
1915 | 0 | error_context_stack = &errcallback; |
1916 | | |
1917 | | /* |
1918 | | * TransamVariables->nextXid must be beyond record's xid. |
1919 | | */ |
1920 | 0 | AdvanceNextFullTransactionIdPastXid(record->xl_xid); |
1921 | | |
1922 | | /* |
1923 | | * Before replaying this record, check if this record causes the current |
1924 | | * timeline to change. The record is already considered to be part of the |
1925 | | * new timeline, so we update replayTLI before replaying it. That's |
1926 | | * important so that replayEndTLI, which is recorded as the minimum |
1927 | | * recovery point's TLI if recovery stops after this record, is set |
1928 | | * correctly. |
1929 | | */ |
1930 | 0 | if (record->xl_rmid == RM_XLOG_ID) |
1931 | 0 | { |
1932 | 0 | TimeLineID newReplayTLI = *replayTLI; |
1933 | 0 | TimeLineID prevReplayTLI = *replayTLI; |
1934 | 0 | uint8 info = record->xl_info & ~XLR_INFO_MASK; |
1935 | |
|
1936 | 0 | if (info == XLOG_CHECKPOINT_SHUTDOWN) |
1937 | 0 | { |
1938 | 0 | CheckPoint checkPoint; |
1939 | |
|
1940 | 0 | memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint)); |
1941 | 0 | newReplayTLI = checkPoint.ThisTimeLineID; |
1942 | 0 | prevReplayTLI = checkPoint.PrevTimeLineID; |
1943 | 0 | } |
1944 | 0 | else if (info == XLOG_END_OF_RECOVERY) |
1945 | 0 | { |
1946 | 0 | xl_end_of_recovery xlrec; |
1947 | |
|
1948 | 0 | memcpy(&xlrec, XLogRecGetData(xlogreader), sizeof(xl_end_of_recovery)); |
1949 | 0 | newReplayTLI = xlrec.ThisTimeLineID; |
1950 | 0 | prevReplayTLI = xlrec.PrevTimeLineID; |
1951 | 0 | } |
1952 | |
|
1953 | 0 | if (newReplayTLI != *replayTLI) |
1954 | 0 | { |
1955 | | /* Check that it's OK to switch to this TLI */ |
1956 | 0 | checkTimeLineSwitch(xlogreader->EndRecPtr, |
1957 | 0 | newReplayTLI, prevReplayTLI, *replayTLI); |
1958 | | |
1959 | | /* Following WAL records should be run with new TLI */ |
1960 | 0 | *replayTLI = newReplayTLI; |
1961 | 0 | switchedTLI = true; |
1962 | 0 | } |
1963 | 0 | } |
1964 | | |
1965 | | /* |
1966 | | * Update shared replayEndRecPtr before replaying this record, so that |
1967 | | * XLogFlush will update minRecoveryPoint correctly. |
1968 | | */ |
1969 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
1970 | 0 | XLogRecoveryCtl->replayEndRecPtr = xlogreader->EndRecPtr; |
1971 | 0 | XLogRecoveryCtl->replayEndTLI = *replayTLI; |
1972 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
1973 | | |
1974 | | /* |
1975 | | * If we are attempting to enter Hot Standby mode, process XIDs we see |
1976 | | */ |
1977 | 0 | if (standbyState >= STANDBY_INITIALIZED && |
1978 | 0 | TransactionIdIsValid(record->xl_xid)) |
1979 | 0 | RecordKnownAssignedTransactionIds(record->xl_xid); |
1980 | | |
1981 | | /* |
1982 | | * Some XLOG record types that are related to recovery are processed |
1983 | | * directly here, rather than in xlog_redo() |
1984 | | */ |
1985 | 0 | if (record->xl_rmid == RM_XLOG_ID) |
1986 | 0 | xlogrecovery_redo(xlogreader, *replayTLI); |
1987 | | |
1988 | | /* Now apply the WAL record itself */ |
1989 | 0 | GetRmgr(record->xl_rmid).rm_redo(xlogreader); |
1990 | | |
1991 | | /* |
1992 | | * After redo, check whether the backup pages associated with the WAL |
1993 | | * record are consistent with the existing pages. This check is done only |
1994 | | * if consistency check is enabled for this record. |
1995 | | */ |
1996 | 0 | if ((record->xl_info & XLR_CHECK_CONSISTENCY) != 0) |
1997 | 0 | verifyBackupPageConsistency(xlogreader); |
1998 | | |
1999 | | /* Pop the error context stack */ |
2000 | 0 | error_context_stack = errcallback.previous; |
2001 | | |
2002 | | /* |
2003 | | * Update lastReplayedEndRecPtr after this record has been successfully |
2004 | | * replayed. |
2005 | | */ |
2006 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
2007 | 0 | XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr; |
2008 | 0 | XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr; |
2009 | 0 | XLogRecoveryCtl->lastReplayedTLI = *replayTLI; |
2010 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
2011 | | |
2012 | | /* ------ |
2013 | | * Wakeup walsenders: |
2014 | | * |
2015 | | * On the standby, the WAL is flushed first (which will only wake up |
2016 | | * physical walsenders) and then applied, which will only wake up logical |
2017 | | * walsenders. |
2018 | | * |
2019 | | * Indeed, logical walsenders on standby can't decode and send data until |
2020 | | * it's been applied. |
2021 | | * |
2022 | | * Physical walsenders don't need to be woken up during replay unless |
2023 | | * cascading replication is allowed and time line change occurred (so that |
2024 | | * they can notice that they are on a new time line). |
2025 | | * |
2026 | | * That's why the wake up conditions are for: |
2027 | | * |
2028 | | * - physical walsenders in case of new time line and cascade |
2029 | | * replication is allowed |
2030 | | * - logical walsenders in case cascade replication is allowed (could not |
2031 | | * be created otherwise) |
2032 | | * ------ |
2033 | | */ |
2034 | 0 | if (AllowCascadeReplication()) |
2035 | 0 | WalSndWakeup(switchedTLI, true); |
2036 | | |
2037 | | /* |
2038 | | * If rm_redo called XLogRequestWalReceiverReply, then we wake up the |
2039 | | * receiver so that it notices the updated lastReplayedEndRecPtr and sends |
2040 | | * a reply to the primary. |
2041 | | */ |
2042 | 0 | if (doRequestWalReceiverReply) |
2043 | 0 | { |
2044 | 0 | doRequestWalReceiverReply = false; |
2045 | 0 | WalRcvRequestApplyReply(); |
2046 | 0 | } |
2047 | | |
2048 | | /* Allow read-only connections if we're consistent now */ |
2049 | 0 | CheckRecoveryConsistency(); |
2050 | | |
2051 | | /* Is this a timeline switch? */ |
2052 | 0 | if (switchedTLI) |
2053 | 0 | { |
2054 | | /* |
2055 | | * Before we continue on the new timeline, clean up any (possibly |
2056 | | * bogus) future WAL segments on the old timeline. |
2057 | | */ |
2058 | 0 | RemoveNonParentXlogFiles(xlogreader->EndRecPtr, *replayTLI); |
2059 | | |
2060 | | /* Reset the prefetcher. */ |
2061 | 0 | XLogPrefetchReconfigure(); |
2062 | 0 | } |
2063 | 0 | } |
2064 | | |
2065 | | /* |
2066 | | * Some XLOG RM record types that are directly related to WAL recovery are |
2067 | | * handled here rather than in the xlog_redo() |
2068 | | */ |
2069 | | static void |
2070 | | xlogrecovery_redo(XLogReaderState *record, TimeLineID replayTLI) |
2071 | | { |
2072 | | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
2073 | | XLogRecPtr lsn = record->EndRecPtr; |
2074 | | |
2075 | | Assert(XLogRecGetRmid(record) == RM_XLOG_ID); |
2076 | | |
2077 | | if (info == XLOG_OVERWRITE_CONTRECORD) |
2078 | | { |
2079 | | /* Verify the payload of a XLOG_OVERWRITE_CONTRECORD record. */ |
2080 | | xl_overwrite_contrecord xlrec; |
2081 | | |
2082 | | memcpy(&xlrec, XLogRecGetData(record), sizeof(xl_overwrite_contrecord)); |
2083 | | if (xlrec.overwritten_lsn != record->overwrittenRecPtr) |
2084 | | elog(FATAL, "mismatching overwritten LSN %X/%08X -> %X/%08X", |
2085 | | LSN_FORMAT_ARGS(xlrec.overwritten_lsn), |
2086 | | LSN_FORMAT_ARGS(record->overwrittenRecPtr)); |
2087 | | |
2088 | | /* We have safely skipped the aborted record */ |
2089 | | abortedRecPtr = InvalidXLogRecPtr; |
2090 | | missingContrecPtr = InvalidXLogRecPtr; |
2091 | | |
2092 | | ereport(LOG, |
2093 | | errmsg("successfully skipped missing contrecord at %X/%08X, overwritten at %s", |
2094 | | LSN_FORMAT_ARGS(xlrec.overwritten_lsn), |
2095 | | timestamptz_to_str(xlrec.overwrite_time))); |
2096 | | |
2097 | | /* Verifying the record should only happen once */ |
2098 | | record->overwrittenRecPtr = InvalidXLogRecPtr; |
2099 | | } |
2100 | | else if (info == XLOG_BACKUP_END) |
2101 | | { |
2102 | | XLogRecPtr startpoint; |
2103 | | |
2104 | | memcpy(&startpoint, XLogRecGetData(record), sizeof(startpoint)); |
2105 | | |
2106 | | if (backupStartPoint == startpoint) |
2107 | | { |
2108 | | /* |
2109 | | * We have reached the end of base backup, the point where |
2110 | | * pg_backup_stop() was done. The data on disk is now consistent |
2111 | | * (assuming we have also reached minRecoveryPoint). Set |
2112 | | * backupEndPoint to the current LSN, so that the next call to |
2113 | | * CheckRecoveryConsistency() will notice it and do the |
2114 | | * end-of-backup processing. |
2115 | | */ |
2116 | | elog(DEBUG1, "end of backup record reached"); |
2117 | | |
2118 | | backupEndPoint = lsn; |
2119 | | } |
2120 | | else |
2121 | | elog(DEBUG1, "saw end-of-backup record for backup starting at %X/%08X, waiting for %X/%08X", |
2122 | | LSN_FORMAT_ARGS(startpoint), LSN_FORMAT_ARGS(backupStartPoint)); |
2123 | | } |
2124 | | } |
2125 | | |
2126 | | /* |
2127 | | * Verify that, in non-test mode, ./pg_tblspc doesn't contain any real |
2128 | | * directories. |
2129 | | * |
2130 | | * Replay of database creation XLOG records for databases that were later |
2131 | | * dropped can create fake directories in pg_tblspc. By the time consistency |
2132 | | * is reached these directories should have been removed; here we verify |
2133 | | * that this did indeed happen. This is to be called at the point where |
2134 | | * consistent state is reached. |
2135 | | * |
2136 | | * allow_in_place_tablespaces turns the PANIC into a WARNING, which is |
2137 | | * useful for testing purposes, and also allows for an escape hatch in case |
2138 | | * things go south. |
2139 | | */ |
2140 | | static void |
2141 | | CheckTablespaceDirectory(void) |
2142 | 0 | { |
2143 | 0 | DIR *dir; |
2144 | 0 | struct dirent *de; |
2145 | |
|
2146 | 0 | dir = AllocateDir(PG_TBLSPC_DIR); |
2147 | 0 | while ((de = ReadDir(dir, PG_TBLSPC_DIR)) != NULL) |
2148 | 0 | { |
2149 | 0 | char path[MAXPGPATH + sizeof(PG_TBLSPC_DIR)]; |
2150 | | |
2151 | | /* Skip entries of non-oid names */ |
2152 | 0 | if (strspn(de->d_name, "0123456789") != strlen(de->d_name)) |
2153 | 0 | continue; |
2154 | | |
2155 | 0 | snprintf(path, sizeof(path), "%s/%s", PG_TBLSPC_DIR, de->d_name); |
2156 | |
|
2157 | 0 | if (get_dirent_type(path, de, false, ERROR) != PGFILETYPE_LNK) |
2158 | 0 | ereport(allow_in_place_tablespaces ? WARNING : PANIC, |
2159 | 0 | (errcode(ERRCODE_DATA_CORRUPTED), |
2160 | 0 | errmsg("unexpected directory entry \"%s\" found in %s", |
2161 | 0 | de->d_name, PG_TBLSPC_DIR), |
2162 | 0 | errdetail("All directory entries in %s/ should be symbolic links.", |
2163 | 0 | PG_TBLSPC_DIR), |
2164 | 0 | errhint("Remove those directories, or set \"allow_in_place_tablespaces\" to ON transiently to let recovery complete."))); |
2165 | 0 | } |
2166 | 0 | FreeDir(dir); |
2167 | 0 | } |
2168 | | |
2169 | | /* |
2170 | | * Checks if recovery has reached a consistent state. When consistency is |
2171 | | * reached and we have a valid starting standby snapshot, tell postmaster |
2172 | | * that it can start accepting read-only connections. |
2173 | | */ |
2174 | | static void |
2175 | | CheckRecoveryConsistency(void) |
2176 | 0 | { |
2177 | 0 | XLogRecPtr lastReplayedEndRecPtr; |
2178 | 0 | TimeLineID lastReplayedTLI; |
2179 | | |
2180 | | /* |
2181 | | * During crash recovery, we don't reach a consistent state until we've |
2182 | | * replayed all the WAL. |
2183 | | */ |
2184 | 0 | if (!XLogRecPtrIsValid(minRecoveryPoint)) |
2185 | 0 | return; |
2186 | | |
2187 | 0 | Assert(InArchiveRecovery); |
2188 | | |
2189 | | /* |
2190 | | * assume that we are called in the startup process, and hence don't need |
2191 | | * a lock to read lastReplayedEndRecPtr |
2192 | | */ |
2193 | 0 | lastReplayedEndRecPtr = XLogRecoveryCtl->lastReplayedEndRecPtr; |
2194 | 0 | lastReplayedTLI = XLogRecoveryCtl->lastReplayedTLI; |
2195 | | |
2196 | | /* |
2197 | | * Have we reached the point where our base backup was completed? |
2198 | | */ |
2199 | 0 | if (XLogRecPtrIsValid(backupEndPoint) && |
2200 | 0 | backupEndPoint <= lastReplayedEndRecPtr) |
2201 | 0 | { |
2202 | 0 | XLogRecPtr saveBackupStartPoint = backupStartPoint; |
2203 | 0 | XLogRecPtr saveBackupEndPoint = backupEndPoint; |
2204 | |
|
2205 | 0 | elog(DEBUG1, "end of backup reached"); |
2206 | | |
2207 | | /* |
2208 | | * We have reached the end of base backup, as indicated by pg_control. |
2209 | | * Update the control file accordingly. |
2210 | | */ |
2211 | 0 | ReachedEndOfBackup(lastReplayedEndRecPtr, lastReplayedTLI); |
2212 | 0 | backupStartPoint = InvalidXLogRecPtr; |
2213 | 0 | backupEndPoint = InvalidXLogRecPtr; |
2214 | 0 | backupEndRequired = false; |
2215 | |
|
2216 | 0 | ereport(LOG, |
2217 | 0 | errmsg("completed backup recovery with redo LSN %X/%08X and end LSN %X/%08X", |
2218 | 0 | LSN_FORMAT_ARGS(saveBackupStartPoint), |
2219 | 0 | LSN_FORMAT_ARGS(saveBackupEndPoint))); |
2220 | 0 | } |
2221 | | |
2222 | | /* |
2223 | | * Have we passed our safe starting point? Note that minRecoveryPoint is |
2224 | | * known to be incorrectly set if recovering from a backup, until the |
2225 | | * XLOG_BACKUP_END arrives to advise us of the correct minRecoveryPoint. |
2226 | | * All we know prior to that is that we're not consistent yet. |
2227 | | */ |
2228 | 0 | if (!reachedConsistency && !backupEndRequired && |
2229 | 0 | minRecoveryPoint <= lastReplayedEndRecPtr) |
2230 | 0 | { |
2231 | | /* |
2232 | | * Check to see if the XLOG sequence contained any unresolved |
2233 | | * references to uninitialized pages. |
2234 | | */ |
2235 | 0 | XLogCheckInvalidPages(); |
2236 | | |
2237 | | /* |
2238 | | * Check that pg_tblspc doesn't contain any real directories. Replay |
2239 | | * of Database/CREATE_* records may have created fictitious tablespace |
2240 | | * directories that should have been removed by the time consistency |
2241 | | * was reached. |
2242 | | */ |
2243 | 0 | CheckTablespaceDirectory(); |
2244 | |
|
2245 | 0 | reachedConsistency = true; |
2246 | 0 | SendPostmasterSignal(PMSIGNAL_RECOVERY_CONSISTENT); |
2247 | 0 | ereport(LOG, |
2248 | 0 | errmsg("consistent recovery state reached at %X/%08X", |
2249 | 0 | LSN_FORMAT_ARGS(lastReplayedEndRecPtr))); |
2250 | 0 | } |
2251 | | |
2252 | | /* |
2253 | | * Have we got a valid starting snapshot that will allow queries to be |
2254 | | * run? If so, we can tell postmaster that the database is consistent now, |
2255 | | * enabling connections. |
2256 | | */ |
2257 | 0 | if (standbyState == STANDBY_SNAPSHOT_READY && |
2258 | 0 | !LocalHotStandbyActive && |
2259 | 0 | reachedConsistency && |
2260 | 0 | IsUnderPostmaster) |
2261 | 0 | { |
2262 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
2263 | 0 | XLogRecoveryCtl->SharedHotStandbyActive = true; |
2264 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
2265 | |
|
2266 | 0 | LocalHotStandbyActive = true; |
2267 | |
|
2268 | 0 | SendPostmasterSignal(PMSIGNAL_BEGIN_HOT_STANDBY); |
2269 | 0 | } |
2270 | 0 | } |
2271 | | |
2272 | | /* |
2273 | | * Error context callback for errors occurring during rm_redo(). |
2274 | | */ |
2275 | | static void |
2276 | | rm_redo_error_callback(void *arg) |
2277 | 0 | { |
2278 | 0 | XLogReaderState *record = (XLogReaderState *) arg; |
2279 | 0 | StringInfoData buf; |
2280 | |
|
2281 | 0 | initStringInfo(&buf); |
2282 | 0 | xlog_outdesc(&buf, record); |
2283 | 0 | xlog_block_info(&buf, record); |
2284 | | |
2285 | | /* translator: %s is a WAL record description */ |
2286 | 0 | errcontext("WAL redo at %X/%08X for %s", |
2287 | 0 | LSN_FORMAT_ARGS(record->ReadRecPtr), |
2288 | 0 | buf.data); |
2289 | |
|
2290 | 0 | pfree(buf.data); |
2291 | 0 | } |
2292 | | |
2293 | | /* |
2294 | | * Returns a string describing an XLogRecord, consisting of its identity |
2295 | | * optionally followed by a colon, a space, and a further description. |
2296 | | */ |
2297 | | void |
2298 | | xlog_outdesc(StringInfo buf, XLogReaderState *record) |
2299 | 0 | { |
2300 | 0 | RmgrData rmgr = GetRmgr(XLogRecGetRmid(record)); |
2301 | 0 | uint8 info = XLogRecGetInfo(record); |
2302 | 0 | const char *id; |
2303 | |
|
2304 | 0 | appendStringInfoString(buf, rmgr.rm_name); |
2305 | 0 | appendStringInfoChar(buf, '/'); |
2306 | |
|
2307 | 0 | id = rmgr.rm_identify(info); |
2308 | 0 | if (id == NULL) |
2309 | 0 | appendStringInfo(buf, "UNKNOWN (%X): ", info & ~XLR_INFO_MASK); |
2310 | 0 | else |
2311 | 0 | appendStringInfo(buf, "%s: ", id); |
2312 | |
|
2313 | 0 | rmgr.rm_desc(buf, record); |
2314 | 0 | } |
2315 | | |
2316 | | #ifdef WAL_DEBUG |
2317 | | |
2318 | | static void |
2319 | | xlog_outrec(StringInfo buf, XLogReaderState *record) |
2320 | | { |
2321 | | appendStringInfo(buf, "prev %X/%08X; xid %u", |
2322 | | LSN_FORMAT_ARGS(XLogRecGetPrev(record)), |
2323 | | XLogRecGetXid(record)); |
2324 | | |
2325 | | appendStringInfo(buf, "; len %u", |
2326 | | XLogRecGetDataLen(record)); |
2327 | | |
2328 | | xlog_block_info(buf, record); |
2329 | | } |
2330 | | #endif /* WAL_DEBUG */ |
2331 | | |
2332 | | /* |
2333 | | * Returns a string giving information about all the blocks in an |
2334 | | * XLogRecord. |
2335 | | */ |
2336 | | static void |
2337 | | xlog_block_info(StringInfo buf, XLogReaderState *record) |
2338 | 0 | { |
2339 | 0 | int block_id; |
2340 | | |
2341 | | /* decode block references */ |
2342 | 0 | for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++) |
2343 | 0 | { |
2344 | 0 | RelFileLocator rlocator; |
2345 | 0 | ForkNumber forknum; |
2346 | 0 | BlockNumber blk; |
2347 | |
|
2348 | 0 | if (!XLogRecGetBlockTagExtended(record, block_id, |
2349 | 0 | &rlocator, &forknum, &blk, NULL)) |
2350 | 0 | continue; |
2351 | | |
2352 | 0 | if (forknum != MAIN_FORKNUM) |
2353 | 0 | appendStringInfo(buf, "; blkref #%d: rel %u/%u/%u, fork %u, blk %u", |
2354 | 0 | block_id, |
2355 | 0 | rlocator.spcOid, rlocator.dbOid, |
2356 | 0 | rlocator.relNumber, |
2357 | 0 | forknum, |
2358 | 0 | blk); |
2359 | 0 | else |
2360 | 0 | appendStringInfo(buf, "; blkref #%d: rel %u/%u/%u, blk %u", |
2361 | 0 | block_id, |
2362 | 0 | rlocator.spcOid, rlocator.dbOid, |
2363 | 0 | rlocator.relNumber, |
2364 | 0 | blk); |
2365 | 0 | if (XLogRecHasBlockImage(record, block_id)) |
2366 | 0 | appendStringInfoString(buf, " FPW"); |
2367 | 0 | } |
2368 | 0 | } |
2369 | | |
2370 | | |
2371 | | /* |
2372 | | * Check that it's OK to switch to new timeline during recovery. |
2373 | | * |
2374 | | * 'lsn' is the address of the shutdown checkpoint record we're about to |
2375 | | * replay. (Currently, timeline can only change at a shutdown checkpoint). |
2376 | | */ |
2377 | | static void |
2378 | | checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI, TimeLineID prevTLI, |
2379 | | TimeLineID replayTLI) |
2380 | 0 | { |
2381 | | /* Check that the record agrees on what the current (old) timeline is */ |
2382 | 0 | if (prevTLI != replayTLI) |
2383 | 0 | ereport(PANIC, |
2384 | 0 | (errmsg("unexpected previous timeline ID %u (current timeline ID %u) in checkpoint record", |
2385 | 0 | prevTLI, replayTLI))); |
2386 | | |
2387 | | /* |
2388 | | * The new timeline better be in the list of timelines we expect to see, |
2389 | | * according to the timeline history. It should also not decrease. |
2390 | | */ |
2391 | 0 | if (newTLI < replayTLI || !tliInHistory(newTLI, expectedTLEs)) |
2392 | 0 | ereport(PANIC, |
2393 | 0 | (errmsg("unexpected timeline ID %u (after %u) in checkpoint record", |
2394 | 0 | newTLI, replayTLI))); |
2395 | | |
2396 | | /* |
2397 | | * If we have not yet reached min recovery point, and we're about to |
2398 | | * switch to a timeline greater than the timeline of the min recovery |
2399 | | * point: trouble. After switching to the new timeline, we could not |
2400 | | * possibly visit the min recovery point on the correct timeline anymore. |
2401 | | * This can happen if there is a newer timeline in the archive that |
2402 | | * branched before the timeline the min recovery point is on, and you |
2403 | | * attempt to do PITR to the new timeline. |
2404 | | */ |
2405 | 0 | if (XLogRecPtrIsValid(minRecoveryPoint) && |
2406 | 0 | lsn < minRecoveryPoint && |
2407 | 0 | newTLI > minRecoveryPointTLI) |
2408 | 0 | ereport(PANIC, |
2409 | 0 | errmsg("unexpected timeline ID %u in checkpoint record, before reaching minimum recovery point %X/%08X on timeline %u", |
2410 | 0 | newTLI, |
2411 | 0 | LSN_FORMAT_ARGS(minRecoveryPoint), |
2412 | 0 | minRecoveryPointTLI)); |
2413 | | |
2414 | | /* Looks good */ |
2415 | 0 | } |
2416 | | |
2417 | | |
2418 | | /* |
2419 | | * Extract timestamp from WAL record. |
2420 | | * |
2421 | | * If the record contains a timestamp, returns true, and saves the timestamp |
2422 | | * in *recordXtime. If the record type has no timestamp, returns false. |
2423 | | * Currently, only transaction commit/abort records and restore points contain |
2424 | | * timestamps. |
2425 | | */ |
2426 | | static bool |
2427 | | getRecordTimestamp(XLogReaderState *record, TimestampTz *recordXtime) |
2428 | 0 | { |
2429 | 0 | uint8 info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
2430 | 0 | uint8 xact_info = info & XLOG_XACT_OPMASK; |
2431 | 0 | uint8 rmid = XLogRecGetRmid(record); |
2432 | |
|
2433 | 0 | if (rmid == RM_XLOG_ID && info == XLOG_RESTORE_POINT) |
2434 | 0 | { |
2435 | 0 | *recordXtime = ((xl_restore_point *) XLogRecGetData(record))->rp_time; |
2436 | 0 | return true; |
2437 | 0 | } |
2438 | 0 | if (rmid == RM_XACT_ID && (xact_info == XLOG_XACT_COMMIT || |
2439 | 0 | xact_info == XLOG_XACT_COMMIT_PREPARED)) |
2440 | 0 | { |
2441 | 0 | *recordXtime = ((xl_xact_commit *) XLogRecGetData(record))->xact_time; |
2442 | 0 | return true; |
2443 | 0 | } |
2444 | 0 | if (rmid == RM_XACT_ID && (xact_info == XLOG_XACT_ABORT || |
2445 | 0 | xact_info == XLOG_XACT_ABORT_PREPARED)) |
2446 | 0 | { |
2447 | 0 | *recordXtime = ((xl_xact_abort *) XLogRecGetData(record))->xact_time; |
2448 | 0 | return true; |
2449 | 0 | } |
2450 | 0 | return false; |
2451 | 0 | } |
2452 | | |
2453 | | /* |
2454 | | * Checks whether the current buffer page and backup page stored in the |
2455 | | * WAL record are consistent or not. Before comparing the two pages, a |
2456 | | * masking can be applied to the pages to ignore certain areas like hint bits, |
2457 | | * unused space between pd_lower and pd_upper among other things. This |
2458 | | * function should be called once WAL replay has been completed for a |
2459 | | * given record. |
2460 | | */ |
2461 | | static void |
2462 | | verifyBackupPageConsistency(XLogReaderState *record) |
2463 | 0 | { |
2464 | 0 | RmgrData rmgr = GetRmgr(XLogRecGetRmid(record)); |
2465 | 0 | RelFileLocator rlocator; |
2466 | 0 | ForkNumber forknum; |
2467 | 0 | BlockNumber blkno; |
2468 | 0 | int block_id; |
2469 | | |
2470 | | /* Records with no backup blocks have no need for consistency checks. */ |
2471 | 0 | if (!XLogRecHasAnyBlockRefs(record)) |
2472 | 0 | return; |
2473 | | |
2474 | 0 | Assert((XLogRecGetInfo(record) & XLR_CHECK_CONSISTENCY) != 0); |
2475 | |
|
2476 | 0 | for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++) |
2477 | 0 | { |
2478 | 0 | Buffer buf; |
2479 | 0 | Page page; |
2480 | |
|
2481 | 0 | if (!XLogRecGetBlockTagExtended(record, block_id, |
2482 | 0 | &rlocator, &forknum, &blkno, NULL)) |
2483 | 0 | { |
2484 | | /* |
2485 | | * WAL record doesn't contain a block reference with the given id. |
2486 | | * Do nothing. |
2487 | | */ |
2488 | 0 | continue; |
2489 | 0 | } |
2490 | | |
2491 | | /* |
2492 | | * Blocks registered with REGBUF_NO_CHANGE may have no image, see |
2493 | | * XLogRecordAssemble(). Nothing to compare for these. |
2494 | | */ |
2495 | 0 | if (!XLogRecHasBlockImage(record, block_id)) |
2496 | 0 | continue; |
2497 | | |
2498 | 0 | if (XLogRecBlockImageApply(record, block_id)) |
2499 | 0 | { |
2500 | | /* |
2501 | | * WAL record has already applied the page, so bypass the |
2502 | | * consistency check as that would result in comparing the full |
2503 | | * page stored in the record with itself. |
2504 | | */ |
2505 | 0 | continue; |
2506 | 0 | } |
2507 | | |
2508 | | /* |
2509 | | * Read the contents from the current buffer and store it in a |
2510 | | * temporary page. |
2511 | | */ |
2512 | 0 | buf = XLogReadBufferExtended(rlocator, forknum, blkno, |
2513 | 0 | RBM_NORMAL_NO_LOG, |
2514 | 0 | InvalidBuffer); |
2515 | 0 | if (!BufferIsValid(buf)) |
2516 | 0 | continue; |
2517 | | |
2518 | 0 | LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); |
2519 | 0 | page = BufferGetPage(buf); |
2520 | | |
2521 | | /* |
2522 | | * Take a copy of the local page where WAL has been applied to have a |
2523 | | * comparison base before masking it... |
2524 | | */ |
2525 | 0 | memcpy(replay_image_masked, page, BLCKSZ); |
2526 | | |
2527 | | /* No need for this page anymore now that a copy is in. */ |
2528 | 0 | UnlockReleaseBuffer(buf); |
2529 | | |
2530 | | /* |
2531 | | * If the block LSN is already ahead of this WAL record, we can't |
2532 | | * expect contents to match. This can happen if recovery is |
2533 | | * restarted. |
2534 | | */ |
2535 | 0 | if (PageGetLSN(replay_image_masked) > record->EndRecPtr) |
2536 | 0 | continue; |
2537 | | |
2538 | | /* |
2539 | | * Read the contents from the backup copy, stored in WAL record and |
2540 | | * store it in a temporary page. There is no need to allocate a new |
2541 | | * page here, a local buffer is fine to hold its contents and a mask |
2542 | | * can be directly applied on it. |
2543 | | */ |
2544 | 0 | if (!RestoreBlockImage(record, block_id, primary_image_masked)) |
2545 | 0 | ereport(ERROR, |
2546 | 0 | (errcode(ERRCODE_INTERNAL_ERROR), |
2547 | 0 | errmsg_internal("%s", record->errormsg_buf))); |
2548 | | |
2549 | | /* |
2550 | | * If masking function is defined, mask both the primary and replay |
2551 | | * images |
2552 | | */ |
2553 | 0 | if (rmgr.rm_mask != NULL) |
2554 | 0 | { |
2555 | 0 | rmgr.rm_mask(replay_image_masked, blkno); |
2556 | 0 | rmgr.rm_mask(primary_image_masked, blkno); |
2557 | 0 | } |
2558 | | |
2559 | | /* Time to compare the primary and replay images. */ |
2560 | 0 | if (memcmp(replay_image_masked, primary_image_masked, BLCKSZ) != 0) |
2561 | 0 | { |
2562 | 0 | elog(FATAL, |
2563 | 0 | "inconsistent page found, rel %u/%u/%u, forknum %u, blkno %u", |
2564 | 0 | rlocator.spcOid, rlocator.dbOid, rlocator.relNumber, |
2565 | 0 | forknum, blkno); |
2566 | 0 | } |
2567 | 0 | } |
2568 | 0 | } |
2569 | | |
2570 | | /* |
2571 | | * For point-in-time recovery, this function decides whether we want to |
2572 | | * stop applying the XLOG before the current record. |
2573 | | * |
2574 | | * Returns true if we are stopping, false otherwise. If stopping, some |
2575 | | * information is saved in recoveryStopXid et al for use in annotating the |
2576 | | * new timeline's history file. |
2577 | | */ |
2578 | | static bool |
2579 | | recoveryStopsBefore(XLogReaderState *record) |
2580 | | { |
2581 | | bool stopsHere = false; |
2582 | | uint8 xact_info; |
2583 | | bool isCommit; |
2584 | | TimestampTz recordXtime = 0; |
2585 | | TransactionId recordXid; |
2586 | | |
2587 | | /* |
2588 | | * Ignore recovery target settings when not in archive recovery (meaning |
2589 | | * we are in crash recovery). |
2590 | | */ |
2591 | | if (!ArchiveRecoveryRequested) |
2592 | | return false; |
2593 | | |
2594 | | /* Check if we should stop as soon as reaching consistency */ |
2595 | | if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE && reachedConsistency) |
2596 | | { |
2597 | | ereport(LOG, |
2598 | | (errmsg("recovery stopping after reaching consistency"))); |
2599 | | |
2600 | | recoveryStopAfter = false; |
2601 | | recoveryStopXid = InvalidTransactionId; |
2602 | | recoveryStopLSN = InvalidXLogRecPtr; |
2603 | | recoveryStopTime = 0; |
2604 | | recoveryStopName[0] = '\0'; |
2605 | | return true; |
2606 | | } |
2607 | | |
2608 | | /* Check if target LSN has been reached */ |
2609 | | if (recoveryTarget == RECOVERY_TARGET_LSN && |
2610 | | !recoveryTargetInclusive && |
2611 | | record->ReadRecPtr >= recoveryTargetLSN) |
2612 | | { |
2613 | | recoveryStopAfter = false; |
2614 | | recoveryStopXid = InvalidTransactionId; |
2615 | | recoveryStopLSN = record->ReadRecPtr; |
2616 | | recoveryStopTime = 0; |
2617 | | recoveryStopName[0] = '\0'; |
2618 | | ereport(LOG, |
2619 | | errmsg("recovery stopping before WAL location (LSN) \"%X/%08X\"", |
2620 | | LSN_FORMAT_ARGS(recoveryStopLSN))); |
2621 | | return true; |
2622 | | } |
2623 | | |
2624 | | /* Otherwise we only consider stopping before COMMIT or ABORT records. */ |
2625 | | if (XLogRecGetRmid(record) != RM_XACT_ID) |
2626 | | return false; |
2627 | | |
2628 | | xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK; |
2629 | | |
2630 | | if (xact_info == XLOG_XACT_COMMIT) |
2631 | | { |
2632 | | isCommit = true; |
2633 | | recordXid = XLogRecGetXid(record); |
2634 | | } |
2635 | | else if (xact_info == XLOG_XACT_COMMIT_PREPARED) |
2636 | | { |
2637 | | xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record); |
2638 | | xl_xact_parsed_commit parsed; |
2639 | | |
2640 | | isCommit = true; |
2641 | | ParseCommitRecord(XLogRecGetInfo(record), |
2642 | | xlrec, |
2643 | | &parsed); |
2644 | | recordXid = parsed.twophase_xid; |
2645 | | } |
2646 | | else if (xact_info == XLOG_XACT_ABORT) |
2647 | | { |
2648 | | isCommit = false; |
2649 | | recordXid = XLogRecGetXid(record); |
2650 | | } |
2651 | | else if (xact_info == XLOG_XACT_ABORT_PREPARED) |
2652 | | { |
2653 | | xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record); |
2654 | | xl_xact_parsed_abort parsed; |
2655 | | |
2656 | | isCommit = false; |
2657 | | ParseAbortRecord(XLogRecGetInfo(record), |
2658 | | xlrec, |
2659 | | &parsed); |
2660 | | recordXid = parsed.twophase_xid; |
2661 | | } |
2662 | | else |
2663 | | return false; |
2664 | | |
2665 | | if (recoveryTarget == RECOVERY_TARGET_XID && !recoveryTargetInclusive) |
2666 | | { |
2667 | | /* |
2668 | | * There can be only one transaction end record with this exact |
2669 | | * transactionid |
2670 | | * |
2671 | | * when testing for an xid, we MUST test for equality only, since |
2672 | | * transactions are numbered in the order they start, not the order |
2673 | | * they complete. A higher numbered xid will complete before you about |
2674 | | * 50% of the time... |
2675 | | */ |
2676 | | stopsHere = (recordXid == recoveryTargetXid); |
2677 | | } |
2678 | | |
2679 | | /* |
2680 | | * Note: we must fetch recordXtime regardless of recoveryTarget setting. |
2681 | | * We don't expect getRecordTimestamp ever to fail, since we already know |
2682 | | * this is a commit or abort record; but test its result anyway. |
2683 | | */ |
2684 | | if (getRecordTimestamp(record, &recordXtime) && |
2685 | | recoveryTarget == RECOVERY_TARGET_TIME) |
2686 | | { |
2687 | | /* |
2688 | | * There can be many transactions that share the same commit time, so |
2689 | | * we stop after the last one, if we are inclusive, or stop at the |
2690 | | * first one if we are exclusive |
2691 | | */ |
2692 | | if (recoveryTargetInclusive) |
2693 | | stopsHere = (recordXtime > recoveryTargetTime); |
2694 | | else |
2695 | | stopsHere = (recordXtime >= recoveryTargetTime); |
2696 | | } |
2697 | | |
2698 | | if (stopsHere) |
2699 | | { |
2700 | | recoveryStopAfter = false; |
2701 | | recoveryStopXid = recordXid; |
2702 | | recoveryStopTime = recordXtime; |
2703 | | recoveryStopLSN = InvalidXLogRecPtr; |
2704 | | recoveryStopName[0] = '\0'; |
2705 | | |
2706 | | if (isCommit) |
2707 | | { |
2708 | | ereport(LOG, |
2709 | | (errmsg("recovery stopping before commit of transaction %u, time %s", |
2710 | | recoveryStopXid, |
2711 | | timestamptz_to_str(recoveryStopTime)))); |
2712 | | } |
2713 | | else |
2714 | | { |
2715 | | ereport(LOG, |
2716 | | (errmsg("recovery stopping before abort of transaction %u, time %s", |
2717 | | recoveryStopXid, |
2718 | | timestamptz_to_str(recoveryStopTime)))); |
2719 | | } |
2720 | | } |
2721 | | |
2722 | | return stopsHere; |
2723 | | } |
2724 | | |
2725 | | /* |
2726 | | * Same as recoveryStopsBefore, but called after applying the record. |
2727 | | * |
2728 | | * We also track the timestamp of the latest applied COMMIT/ABORT |
2729 | | * record in XLogRecoveryCtl->recoveryLastXTime. |
2730 | | */ |
2731 | | static bool |
2732 | | recoveryStopsAfter(XLogReaderState *record) |
2733 | | { |
2734 | | uint8 info; |
2735 | | uint8 xact_info; |
2736 | | uint8 rmid; |
2737 | | TimestampTz recordXtime = 0; |
2738 | | |
2739 | | /* |
2740 | | * Ignore recovery target settings when not in archive recovery (meaning |
2741 | | * we are in crash recovery). |
2742 | | */ |
2743 | | if (!ArchiveRecoveryRequested) |
2744 | | return false; |
2745 | | |
2746 | | info = XLogRecGetInfo(record) & ~XLR_INFO_MASK; |
2747 | | rmid = XLogRecGetRmid(record); |
2748 | | |
2749 | | /* |
2750 | | * There can be many restore points that share the same name; we stop at |
2751 | | * the first one. |
2752 | | */ |
2753 | | if (recoveryTarget == RECOVERY_TARGET_NAME && |
2754 | | rmid == RM_XLOG_ID && info == XLOG_RESTORE_POINT) |
2755 | | { |
2756 | | xl_restore_point *recordRestorePointData; |
2757 | | |
2758 | | recordRestorePointData = (xl_restore_point *) XLogRecGetData(record); |
2759 | | |
2760 | | if (strcmp(recordRestorePointData->rp_name, recoveryTargetName) == 0) |
2761 | | { |
2762 | | recoveryStopAfter = true; |
2763 | | recoveryStopXid = InvalidTransactionId; |
2764 | | recoveryStopLSN = InvalidXLogRecPtr; |
2765 | | (void) getRecordTimestamp(record, &recoveryStopTime); |
2766 | | strlcpy(recoveryStopName, recordRestorePointData->rp_name, MAXFNAMELEN); |
2767 | | |
2768 | | ereport(LOG, |
2769 | | (errmsg("recovery stopping at restore point \"%s\", time %s", |
2770 | | recoveryStopName, |
2771 | | timestamptz_to_str(recoveryStopTime)))); |
2772 | | return true; |
2773 | | } |
2774 | | } |
2775 | | |
2776 | | /* Check if the target LSN has been reached */ |
2777 | | if (recoveryTarget == RECOVERY_TARGET_LSN && |
2778 | | recoveryTargetInclusive && |
2779 | | record->ReadRecPtr >= recoveryTargetLSN) |
2780 | | { |
2781 | | recoveryStopAfter = true; |
2782 | | recoveryStopXid = InvalidTransactionId; |
2783 | | recoveryStopLSN = record->ReadRecPtr; |
2784 | | recoveryStopTime = 0; |
2785 | | recoveryStopName[0] = '\0'; |
2786 | | ereport(LOG, |
2787 | | errmsg("recovery stopping after WAL location (LSN) \"%X/%08X\"", |
2788 | | LSN_FORMAT_ARGS(recoveryStopLSN))); |
2789 | | return true; |
2790 | | } |
2791 | | |
2792 | | if (rmid != RM_XACT_ID) |
2793 | | return false; |
2794 | | |
2795 | | xact_info = info & XLOG_XACT_OPMASK; |
2796 | | |
2797 | | if (xact_info == XLOG_XACT_COMMIT || |
2798 | | xact_info == XLOG_XACT_COMMIT_PREPARED || |
2799 | | xact_info == XLOG_XACT_ABORT || |
2800 | | xact_info == XLOG_XACT_ABORT_PREPARED) |
2801 | | { |
2802 | | TransactionId recordXid; |
2803 | | |
2804 | | /* Update the last applied transaction timestamp */ |
2805 | | if (getRecordTimestamp(record, &recordXtime)) |
2806 | | SetLatestXTime(recordXtime); |
2807 | | |
2808 | | /* Extract the XID of the committed/aborted transaction */ |
2809 | | if (xact_info == XLOG_XACT_COMMIT_PREPARED) |
2810 | | { |
2811 | | xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record); |
2812 | | xl_xact_parsed_commit parsed; |
2813 | | |
2814 | | ParseCommitRecord(XLogRecGetInfo(record), |
2815 | | xlrec, |
2816 | | &parsed); |
2817 | | recordXid = parsed.twophase_xid; |
2818 | | } |
2819 | | else if (xact_info == XLOG_XACT_ABORT_PREPARED) |
2820 | | { |
2821 | | xl_xact_abort *xlrec = (xl_xact_abort *) XLogRecGetData(record); |
2822 | | xl_xact_parsed_abort parsed; |
2823 | | |
2824 | | ParseAbortRecord(XLogRecGetInfo(record), |
2825 | | xlrec, |
2826 | | &parsed); |
2827 | | recordXid = parsed.twophase_xid; |
2828 | | } |
2829 | | else |
2830 | | recordXid = XLogRecGetXid(record); |
2831 | | |
2832 | | /* |
2833 | | * There can be only one transaction end record with this exact |
2834 | | * transactionid |
2835 | | * |
2836 | | * when testing for an xid, we MUST test for equality only, since |
2837 | | * transactions are numbered in the order they start, not the order |
2838 | | * they complete. A higher numbered xid will complete before you about |
2839 | | * 50% of the time... |
2840 | | */ |
2841 | | if (recoveryTarget == RECOVERY_TARGET_XID && recoveryTargetInclusive && |
2842 | | recordXid == recoveryTargetXid) |
2843 | | { |
2844 | | recoveryStopAfter = true; |
2845 | | recoveryStopXid = recordXid; |
2846 | | recoveryStopTime = recordXtime; |
2847 | | recoveryStopLSN = InvalidXLogRecPtr; |
2848 | | recoveryStopName[0] = '\0'; |
2849 | | |
2850 | | if (xact_info == XLOG_XACT_COMMIT || |
2851 | | xact_info == XLOG_XACT_COMMIT_PREPARED) |
2852 | | { |
2853 | | ereport(LOG, |
2854 | | (errmsg("recovery stopping after commit of transaction %u, time %s", |
2855 | | recoveryStopXid, |
2856 | | timestamptz_to_str(recoveryStopTime)))); |
2857 | | } |
2858 | | else if (xact_info == XLOG_XACT_ABORT || |
2859 | | xact_info == XLOG_XACT_ABORT_PREPARED) |
2860 | | { |
2861 | | ereport(LOG, |
2862 | | (errmsg("recovery stopping after abort of transaction %u, time %s", |
2863 | | recoveryStopXid, |
2864 | | timestamptz_to_str(recoveryStopTime)))); |
2865 | | } |
2866 | | return true; |
2867 | | } |
2868 | | } |
2869 | | |
2870 | | /* Check if we should stop as soon as reaching consistency */ |
2871 | | if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE && reachedConsistency) |
2872 | | { |
2873 | | ereport(LOG, |
2874 | | (errmsg("recovery stopping after reaching consistency"))); |
2875 | | |
2876 | | recoveryStopAfter = true; |
2877 | | recoveryStopXid = InvalidTransactionId; |
2878 | | recoveryStopTime = 0; |
2879 | | recoveryStopLSN = InvalidXLogRecPtr; |
2880 | | recoveryStopName[0] = '\0'; |
2881 | | return true; |
2882 | | } |
2883 | | |
2884 | | return false; |
2885 | | } |
2886 | | |
2887 | | /* |
2888 | | * Create a comment for the history file to explain why and where |
2889 | | * timeline changed. |
2890 | | */ |
2891 | | static char * |
2892 | | getRecoveryStopReason(void) |
2893 | 0 | { |
2894 | 0 | char reason[200]; |
2895 | |
|
2896 | 0 | if (recoveryTarget == RECOVERY_TARGET_XID) |
2897 | 0 | snprintf(reason, sizeof(reason), |
2898 | 0 | "%s transaction %u", |
2899 | 0 | recoveryStopAfter ? "after" : "before", |
2900 | 0 | recoveryStopXid); |
2901 | 0 | else if (recoveryTarget == RECOVERY_TARGET_TIME) |
2902 | 0 | snprintf(reason, sizeof(reason), |
2903 | 0 | "%s %s\n", |
2904 | 0 | recoveryStopAfter ? "after" : "before", |
2905 | 0 | timestamptz_to_str(recoveryStopTime)); |
2906 | 0 | else if (recoveryTarget == RECOVERY_TARGET_LSN) |
2907 | 0 | snprintf(reason, sizeof(reason), |
2908 | 0 | "%s LSN %X/%08X\n", |
2909 | 0 | recoveryStopAfter ? "after" : "before", |
2910 | 0 | LSN_FORMAT_ARGS(recoveryStopLSN)); |
2911 | 0 | else if (recoveryTarget == RECOVERY_TARGET_NAME) |
2912 | 0 | snprintf(reason, sizeof(reason), |
2913 | 0 | "at restore point \"%s\"", |
2914 | 0 | recoveryStopName); |
2915 | 0 | else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE) |
2916 | 0 | snprintf(reason, sizeof(reason), "reached consistency"); |
2917 | 0 | else |
2918 | 0 | snprintf(reason, sizeof(reason), "no recovery target specified"); |
2919 | |
|
2920 | 0 | return pstrdup(reason); |
2921 | 0 | } |
2922 | | |
2923 | | /* |
2924 | | * Wait until shared recoveryPauseState is set to RECOVERY_NOT_PAUSED. |
2925 | | * |
2926 | | * endOfRecovery is true if the recovery target is reached and |
2927 | | * the paused state starts at the end of recovery because of |
2928 | | * recovery_target_action=pause, and false otherwise. |
2929 | | */ |
2930 | | static void |
2931 | | recoveryPausesHere(bool endOfRecovery) |
2932 | 0 | { |
2933 | | /* Don't pause unless users can connect! */ |
2934 | 0 | if (!LocalHotStandbyActive) |
2935 | 0 | return; |
2936 | | |
2937 | | /* Don't pause after standby promotion has been triggered */ |
2938 | 0 | if (LocalPromoteIsTriggered) |
2939 | 0 | return; |
2940 | | |
2941 | 0 | if (endOfRecovery) |
2942 | 0 | ereport(LOG, |
2943 | 0 | (errmsg("pausing at the end of recovery"), |
2944 | 0 | errhint("Execute pg_wal_replay_resume() to promote."))); |
2945 | 0 | else |
2946 | 0 | ereport(LOG, |
2947 | 0 | (errmsg("recovery has paused"), |
2948 | 0 | errhint("Execute pg_wal_replay_resume() to continue."))); |
2949 | | |
2950 | | /* loop until recoveryPauseState is set to RECOVERY_NOT_PAUSED */ |
2951 | 0 | while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED) |
2952 | 0 | { |
2953 | 0 | ProcessStartupProcInterrupts(); |
2954 | 0 | if (CheckForStandbyTrigger()) |
2955 | 0 | return; |
2956 | | |
2957 | | /* |
2958 | | * If recovery pause is requested then set it paused. While we are in |
2959 | | * the loop, user might resume and pause again so set this every time. |
2960 | | */ |
2961 | 0 | ConfirmRecoveryPaused(); |
2962 | | |
2963 | | /* |
2964 | | * We wait on a condition variable that will wake us as soon as the |
2965 | | * pause ends, but we use a timeout so we can check the above exit |
2966 | | * condition periodically too. |
2967 | | */ |
2968 | 0 | ConditionVariableTimedSleep(&XLogRecoveryCtl->recoveryNotPausedCV, 1000, |
2969 | 0 | WAIT_EVENT_RECOVERY_PAUSE); |
2970 | 0 | } |
2971 | 0 | ConditionVariableCancelSleep(); |
2972 | 0 | } |
2973 | | |
2974 | | /* |
2975 | | * When recovery_min_apply_delay is set, we wait long enough to make sure |
2976 | | * certain record types are applied at least that interval behind the primary. |
2977 | | * |
2978 | | * Returns true if we waited. |
2979 | | * |
2980 | | * Note that the delay is calculated between the WAL record log time and |
2981 | | * the current time on standby. We would prefer to keep track of when this |
2982 | | * standby received each WAL record, which would allow a more consistent |
2983 | | * approach and one not affected by time synchronisation issues, but that |
2984 | | * is significantly more effort and complexity for little actual gain in |
2985 | | * usability. |
2986 | | */ |
2987 | | static bool |
2988 | | recoveryApplyDelay(XLogReaderState *record) |
2989 | | { |
2990 | | uint8 xact_info; |
2991 | | TimestampTz xtime; |
2992 | | TimestampTz delayUntil; |
2993 | | long msecs; |
2994 | | |
2995 | | /* nothing to do if no delay configured */ |
2996 | | if (recovery_min_apply_delay <= 0) |
2997 | | return false; |
2998 | | |
2999 | | /* no delay is applied on a database not yet consistent */ |
3000 | | if (!reachedConsistency) |
3001 | | return false; |
3002 | | |
3003 | | /* nothing to do if crash recovery is requested */ |
3004 | | if (!ArchiveRecoveryRequested) |
3005 | | return false; |
3006 | | |
3007 | | /* |
3008 | | * Is it a COMMIT record? |
3009 | | * |
3010 | | * We deliberately choose not to delay aborts since they have no effect on |
3011 | | * MVCC. We already allow replay of records that don't have a timestamp, |
3012 | | * so there is already opportunity for issues caused by early conflicts on |
3013 | | * standbys. |
3014 | | */ |
3015 | | if (XLogRecGetRmid(record) != RM_XACT_ID) |
3016 | | return false; |
3017 | | |
3018 | | xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK; |
3019 | | |
3020 | | if (xact_info != XLOG_XACT_COMMIT && |
3021 | | xact_info != XLOG_XACT_COMMIT_PREPARED) |
3022 | | return false; |
3023 | | |
3024 | | if (!getRecordTimestamp(record, &xtime)) |
3025 | | return false; |
3026 | | |
3027 | | delayUntil = TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay); |
3028 | | |
3029 | | /* |
3030 | | * Exit without arming the latch if it's already past time to apply this |
3031 | | * record |
3032 | | */ |
3033 | | msecs = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), delayUntil); |
3034 | | if (msecs <= 0) |
3035 | | return false; |
3036 | | |
3037 | | while (true) |
3038 | | { |
3039 | | ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
3040 | | |
3041 | | /* This might change recovery_min_apply_delay. */ |
3042 | | ProcessStartupProcInterrupts(); |
3043 | | |
3044 | | if (CheckForStandbyTrigger()) |
3045 | | break; |
3046 | | |
3047 | | /* |
3048 | | * Recalculate delayUntil as recovery_min_apply_delay could have |
3049 | | * changed while waiting in this loop. |
3050 | | */ |
3051 | | delayUntil = TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay); |
3052 | | |
3053 | | /* |
3054 | | * Wait for difference between GetCurrentTimestamp() and delayUntil. |
3055 | | */ |
3056 | | msecs = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), |
3057 | | delayUntil); |
3058 | | |
3059 | | if (msecs <= 0) |
3060 | | break; |
3061 | | |
3062 | | elog(DEBUG2, "recovery apply delay %ld milliseconds", msecs); |
3063 | | |
3064 | | (void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch, |
3065 | | WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, |
3066 | | msecs, |
3067 | | WAIT_EVENT_RECOVERY_APPLY_DELAY); |
3068 | | } |
3069 | | return true; |
3070 | | } |
3071 | | |
3072 | | /* |
3073 | | * Get the current state of the recovery pause request. |
3074 | | */ |
3075 | | RecoveryPauseState |
3076 | | GetRecoveryPauseState(void) |
3077 | 0 | { |
3078 | 0 | RecoveryPauseState state; |
3079 | |
|
3080 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
3081 | 0 | state = XLogRecoveryCtl->recoveryPauseState; |
3082 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
3083 | |
|
3084 | 0 | return state; |
3085 | 0 | } |
3086 | | |
3087 | | /* |
3088 | | * Set the recovery pause state. |
3089 | | * |
3090 | | * If recovery pause is requested then sets the recovery pause state to |
3091 | | * 'pause requested' if it is not already 'paused'. Otherwise, sets it |
3092 | | * to 'not paused' to resume the recovery. The recovery pause will be |
3093 | | * confirmed by the ConfirmRecoveryPaused. |
3094 | | */ |
3095 | | void |
3096 | | SetRecoveryPause(bool recoveryPause) |
3097 | 0 | { |
3098 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
3099 | |
|
3100 | 0 | if (!recoveryPause) |
3101 | 0 | XLogRecoveryCtl->recoveryPauseState = RECOVERY_NOT_PAUSED; |
3102 | 0 | else if (XLogRecoveryCtl->recoveryPauseState == RECOVERY_NOT_PAUSED) |
3103 | 0 | XLogRecoveryCtl->recoveryPauseState = RECOVERY_PAUSE_REQUESTED; |
3104 | |
|
3105 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
3106 | |
|
3107 | 0 | if (!recoveryPause) |
3108 | 0 | ConditionVariableBroadcast(&XLogRecoveryCtl->recoveryNotPausedCV); |
3109 | 0 | } |
3110 | | |
3111 | | /* |
3112 | | * Confirm the recovery pause by setting the recovery pause state to |
3113 | | * RECOVERY_PAUSED. |
3114 | | */ |
3115 | | static void |
3116 | | ConfirmRecoveryPaused(void) |
3117 | 0 | { |
3118 | | /* If recovery pause is requested then set it paused */ |
3119 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
3120 | 0 | if (XLogRecoveryCtl->recoveryPauseState == RECOVERY_PAUSE_REQUESTED) |
3121 | 0 | XLogRecoveryCtl->recoveryPauseState = RECOVERY_PAUSED; |
3122 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
3123 | 0 | } |
3124 | | |
3125 | | |
3126 | | /* |
3127 | | * Attempt to read the next XLOG record. |
3128 | | * |
3129 | | * Before first call, the reader needs to be positioned to the first record |
3130 | | * by calling XLogPrefetcherBeginRead(). |
3131 | | * |
3132 | | * If no valid record is available, returns NULL, or fails if emode is PANIC. |
3133 | | * (emode must be either PANIC, LOG). In standby mode, retries until a valid |
3134 | | * record is available. |
3135 | | */ |
3136 | | static XLogRecord * |
3137 | | ReadRecord(XLogPrefetcher *xlogprefetcher, int emode, |
3138 | | bool fetching_ckpt, TimeLineID replayTLI) |
3139 | 0 | { |
3140 | 0 | XLogRecord *record; |
3141 | 0 | XLogReaderState *xlogreader = XLogPrefetcherGetReader(xlogprefetcher); |
3142 | 0 | XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data; |
3143 | |
|
3144 | 0 | Assert(AmStartupProcess() || !IsUnderPostmaster); |
3145 | | |
3146 | | /* Pass through parameters to XLogPageRead */ |
3147 | 0 | private->fetching_ckpt = fetching_ckpt; |
3148 | 0 | private->emode = emode; |
3149 | 0 | private->randAccess = !XLogRecPtrIsValid(xlogreader->ReadRecPtr); |
3150 | 0 | private->replayTLI = replayTLI; |
3151 | | |
3152 | | /* This is the first attempt to read this page. */ |
3153 | 0 | lastSourceFailed = false; |
3154 | |
|
3155 | 0 | for (;;) |
3156 | 0 | { |
3157 | 0 | char *errormsg; |
3158 | |
|
3159 | 0 | record = XLogPrefetcherReadRecord(xlogprefetcher, &errormsg); |
3160 | 0 | if (record == NULL) |
3161 | 0 | { |
3162 | | /* |
3163 | | * When we find that WAL ends in an incomplete record, keep track |
3164 | | * of that record. After recovery is done, we'll write a record |
3165 | | * to indicate to downstream WAL readers that that portion is to |
3166 | | * be ignored. |
3167 | | * |
3168 | | * However, when ArchiveRecoveryRequested = true, we're going to |
3169 | | * switch to a new timeline at the end of recovery. We will only |
3170 | | * copy WAL over to the new timeline up to the end of the last |
3171 | | * complete record, so if we did this, we would later create an |
3172 | | * overwrite contrecord in the wrong place, breaking everything. |
3173 | | */ |
3174 | 0 | if (!ArchiveRecoveryRequested && |
3175 | 0 | XLogRecPtrIsValid(xlogreader->abortedRecPtr)) |
3176 | 0 | { |
3177 | 0 | abortedRecPtr = xlogreader->abortedRecPtr; |
3178 | 0 | missingContrecPtr = xlogreader->missingContrecPtr; |
3179 | 0 | } |
3180 | |
|
3181 | 0 | if (readFile >= 0) |
3182 | 0 | { |
3183 | 0 | close(readFile); |
3184 | 0 | readFile = -1; |
3185 | 0 | } |
3186 | | |
3187 | | /* |
3188 | | * We only end up here without a message when XLogPageRead() |
3189 | | * failed - in that case we already logged something. In |
3190 | | * StandbyMode that only happens if we have been triggered, so we |
3191 | | * shouldn't loop anymore in that case. |
3192 | | */ |
3193 | 0 | if (errormsg) |
3194 | 0 | ereport(emode_for_corrupt_record(emode, xlogreader->EndRecPtr), |
3195 | 0 | (errmsg_internal("%s", errormsg) /* already translated */ )); |
3196 | 0 | } |
3197 | | |
3198 | | /* |
3199 | | * Check page TLI is one of the expected values. |
3200 | | */ |
3201 | 0 | else if (!tliInHistory(xlogreader->latestPageTLI, expectedTLEs)) |
3202 | 0 | { |
3203 | 0 | char fname[MAXFNAMELEN]; |
3204 | 0 | XLogSegNo segno; |
3205 | 0 | int32 offset; |
3206 | |
|
3207 | 0 | XLByteToSeg(xlogreader->latestPagePtr, segno, wal_segment_size); |
3208 | 0 | offset = XLogSegmentOffset(xlogreader->latestPagePtr, |
3209 | 0 | wal_segment_size); |
3210 | 0 | XLogFileName(fname, xlogreader->seg.ws_tli, segno, |
3211 | 0 | wal_segment_size); |
3212 | 0 | ereport(emode_for_corrupt_record(emode, xlogreader->EndRecPtr), |
3213 | 0 | errmsg("unexpected timeline ID %u in WAL segment %s, LSN %X/%08X, offset %u", |
3214 | 0 | xlogreader->latestPageTLI, |
3215 | 0 | fname, |
3216 | 0 | LSN_FORMAT_ARGS(xlogreader->latestPagePtr), |
3217 | 0 | offset)); |
3218 | 0 | record = NULL; |
3219 | 0 | } |
3220 | | |
3221 | 0 | if (record) |
3222 | 0 | { |
3223 | | /* Great, got a record */ |
3224 | 0 | return record; |
3225 | 0 | } |
3226 | 0 | else |
3227 | 0 | { |
3228 | | /* No valid record available from this source */ |
3229 | 0 | lastSourceFailed = true; |
3230 | | |
3231 | | /* |
3232 | | * If archive recovery was requested, but we were still doing |
3233 | | * crash recovery, switch to archive recovery and retry using the |
3234 | | * offline archive. We have now replayed all the valid WAL in |
3235 | | * pg_wal, so we are presumably now consistent. |
3236 | | * |
3237 | | * We require that there's at least some valid WAL present in |
3238 | | * pg_wal, however (!fetching_ckpt). We could recover using the |
3239 | | * WAL from the archive, even if pg_wal is completely empty, but |
3240 | | * we'd have no idea how far we'd have to replay to reach |
3241 | | * consistency. So err on the safe side and give up. |
3242 | | */ |
3243 | 0 | if (!InArchiveRecovery && ArchiveRecoveryRequested && |
3244 | 0 | !fetching_ckpt) |
3245 | 0 | { |
3246 | 0 | ereport(DEBUG1, |
3247 | 0 | (errmsg_internal("reached end of WAL in pg_wal, entering archive recovery"))); |
3248 | 0 | InArchiveRecovery = true; |
3249 | 0 | if (StandbyModeRequested) |
3250 | 0 | EnableStandbyMode(); |
3251 | |
|
3252 | 0 | SwitchIntoArchiveRecovery(xlogreader->EndRecPtr, replayTLI); |
3253 | 0 | minRecoveryPoint = xlogreader->EndRecPtr; |
3254 | 0 | minRecoveryPointTLI = replayTLI; |
3255 | |
|
3256 | 0 | CheckRecoveryConsistency(); |
3257 | | |
3258 | | /* |
3259 | | * Before we retry, reset lastSourceFailed and currentSource |
3260 | | * so that we will check the archive next. |
3261 | | */ |
3262 | 0 | lastSourceFailed = false; |
3263 | 0 | currentSource = XLOG_FROM_ANY; |
3264 | |
|
3265 | 0 | continue; |
3266 | 0 | } |
3267 | | |
3268 | | /* In standby mode, loop back to retry. Otherwise, give up. */ |
3269 | 0 | if (StandbyMode && !CheckForStandbyTrigger()) |
3270 | 0 | continue; |
3271 | 0 | else |
3272 | 0 | return NULL; |
3273 | 0 | } |
3274 | 0 | } |
3275 | 0 | } |
3276 | | |
3277 | | /* |
3278 | | * Read the XLOG page containing targetPagePtr into readBuf (if not read |
3279 | | * already). Returns number of bytes read, if the page is read successfully, |
3280 | | * or XLREAD_FAIL in case of errors. When errors occur, they are ereport'ed, |
3281 | | * but only if they have not been previously reported. |
3282 | | * |
3283 | | * See XLogReaderRoutine.page_read for more details. |
3284 | | * |
3285 | | * While prefetching, xlogreader->nonblocking may be set. In that case, |
3286 | | * returns XLREAD_WOULDBLOCK if we'd otherwise have to wait for more WAL. |
3287 | | * |
3288 | | * This is responsible for restoring files from archive as needed, as well |
3289 | | * as for waiting for the requested WAL record to arrive in standby mode. |
3290 | | * |
3291 | | * xlogreader->private_data->emode specifies the log level used for reporting |
3292 | | * "file not found" or "end of WAL" situations in archive recovery, or in |
3293 | | * standby mode when promotion is triggered. If set to WARNING or below, |
3294 | | * XLogPageRead() returns XLREAD_FAIL in those situations, on higher log |
3295 | | * levels the ereport() won't return. |
3296 | | * |
3297 | | * In standby mode, if after a successful return of XLogPageRead() the |
3298 | | * caller finds the record it's interested in to be broken, it should |
3299 | | * ereport the error with the level determined by |
3300 | | * emode_for_corrupt_record(), and then set lastSourceFailed |
3301 | | * and call XLogPageRead() again with the same arguments. This lets |
3302 | | * XLogPageRead() to try fetching the record from another source, or to |
3303 | | * sleep and retry. |
3304 | | */ |
3305 | | static int |
3306 | | XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, |
3307 | | XLogRecPtr targetRecPtr, char *readBuf) |
3308 | 0 | { |
3309 | 0 | XLogPageReadPrivate *private = |
3310 | 0 | (XLogPageReadPrivate *) xlogreader->private_data; |
3311 | 0 | int emode = private->emode; |
3312 | 0 | uint32 targetPageOff; |
3313 | 0 | XLogSegNo targetSegNo PG_USED_FOR_ASSERTS_ONLY; |
3314 | 0 | ssize_t r; |
3315 | 0 | instr_time io_start; |
3316 | |
|
3317 | 0 | Assert(AmStartupProcess() || !IsUnderPostmaster); |
3318 | |
|
3319 | 0 | XLByteToSeg(targetPagePtr, targetSegNo, wal_segment_size); |
3320 | 0 | targetPageOff = XLogSegmentOffset(targetPagePtr, wal_segment_size); |
3321 | | |
3322 | | /* |
3323 | | * See if we need to switch to a new segment because the requested record |
3324 | | * is not in the currently open one. |
3325 | | */ |
3326 | 0 | if (readFile >= 0 && |
3327 | 0 | !XLByteInSeg(targetPagePtr, readSegNo, wal_segment_size)) |
3328 | 0 | { |
3329 | | /* |
3330 | | * Request a restartpoint if we've replayed too much xlog since the |
3331 | | * last one. |
3332 | | */ |
3333 | 0 | if (ArchiveRecoveryRequested && IsUnderPostmaster) |
3334 | 0 | { |
3335 | 0 | if (XLogCheckpointNeeded(readSegNo)) |
3336 | 0 | { |
3337 | 0 | (void) GetRedoRecPtr(); |
3338 | 0 | if (XLogCheckpointNeeded(readSegNo)) |
3339 | 0 | RequestCheckpoint(CHECKPOINT_CAUSE_XLOG); |
3340 | 0 | } |
3341 | 0 | } |
3342 | |
|
3343 | 0 | close(readFile); |
3344 | 0 | readFile = -1; |
3345 | 0 | readSource = XLOG_FROM_ANY; |
3346 | 0 | } |
3347 | |
|
3348 | 0 | XLByteToSeg(targetPagePtr, readSegNo, wal_segment_size); |
3349 | |
|
3350 | 0 | retry: |
3351 | | /* See if we need to retrieve more data */ |
3352 | 0 | if (readFile < 0 || |
3353 | 0 | (readSource == XLOG_FROM_STREAM && |
3354 | 0 | flushedUpto < targetPagePtr + reqLen)) |
3355 | 0 | { |
3356 | 0 | if (readFile >= 0 && |
3357 | 0 | xlogreader->nonblocking && |
3358 | 0 | readSource == XLOG_FROM_STREAM && |
3359 | 0 | flushedUpto < targetPagePtr + reqLen) |
3360 | 0 | return XLREAD_WOULDBLOCK; |
3361 | | |
3362 | 0 | switch (WaitForWALToBecomeAvailable(targetPagePtr + reqLen, |
3363 | 0 | private->randAccess, |
3364 | 0 | private->fetching_ckpt, |
3365 | 0 | targetRecPtr, |
3366 | 0 | private->replayTLI, |
3367 | 0 | xlogreader->EndRecPtr, |
3368 | 0 | xlogreader->nonblocking)) |
3369 | 0 | { |
3370 | 0 | case XLREAD_WOULDBLOCK: |
3371 | 0 | return XLREAD_WOULDBLOCK; |
3372 | 0 | case XLREAD_FAIL: |
3373 | 0 | if (readFile >= 0) |
3374 | 0 | close(readFile); |
3375 | 0 | readFile = -1; |
3376 | 0 | readLen = 0; |
3377 | 0 | readSource = XLOG_FROM_ANY; |
3378 | 0 | return XLREAD_FAIL; |
3379 | 0 | case XLREAD_SUCCESS: |
3380 | 0 | break; |
3381 | 0 | } |
3382 | 0 | } |
3383 | | |
3384 | | /* |
3385 | | * At this point, we have the right segment open and if we're streaming we |
3386 | | * know the requested record is in it. |
3387 | | */ |
3388 | 0 | Assert(readFile != -1); |
3389 | | |
3390 | | /* |
3391 | | * If the current segment is being streamed from the primary, calculate |
3392 | | * how much of the current page we have received already. We know the |
3393 | | * requested record has been received, but this is for the benefit of |
3394 | | * future calls, to allow quick exit at the top of this function. |
3395 | | */ |
3396 | 0 | if (readSource == XLOG_FROM_STREAM) |
3397 | 0 | { |
3398 | 0 | if (((targetPagePtr) / XLOG_BLCKSZ) != (flushedUpto / XLOG_BLCKSZ)) |
3399 | 0 | readLen = XLOG_BLCKSZ; |
3400 | 0 | else |
3401 | 0 | readLen = XLogSegmentOffset(flushedUpto, wal_segment_size) - |
3402 | 0 | targetPageOff; |
3403 | 0 | } |
3404 | 0 | else |
3405 | 0 | readLen = XLOG_BLCKSZ; |
3406 | | |
3407 | | /* Read the requested page */ |
3408 | 0 | readOff = targetPageOff; |
3409 | | |
3410 | | /* Measure I/O timing when reading segment */ |
3411 | 0 | io_start = pgstat_prepare_io_time(track_wal_io_timing); |
3412 | |
|
3413 | 0 | pgstat_report_wait_start(WAIT_EVENT_WAL_READ); |
3414 | 0 | r = pg_pread(readFile, readBuf, XLOG_BLCKSZ, (pgoff_t) readOff); |
3415 | 0 | if (r != XLOG_BLCKSZ) |
3416 | 0 | { |
3417 | 0 | char fname[MAXFNAMELEN]; |
3418 | 0 | int save_errno = errno; |
3419 | |
|
3420 | 0 | pgstat_report_wait_end(); |
3421 | | |
3422 | | /* Count I/O stats only for successful short reads */ |
3423 | 0 | if (r > 0) |
3424 | 0 | pgstat_count_io_op_time(IOOBJECT_WAL, IOCONTEXT_NORMAL, IOOP_READ, |
3425 | 0 | io_start, 1, r); |
3426 | |
|
3427 | 0 | XLogFileName(fname, curFileTLI, readSegNo, wal_segment_size); |
3428 | 0 | if (r < 0) |
3429 | 0 | { |
3430 | 0 | errno = save_errno; |
3431 | 0 | ereport(emode_for_corrupt_record(emode, targetPagePtr + reqLen), |
3432 | 0 | (errcode_for_file_access(), |
3433 | 0 | errmsg("could not read from WAL segment %s, LSN %X/%08X, offset %u: %m", |
3434 | 0 | fname, LSN_FORMAT_ARGS(targetPagePtr), |
3435 | 0 | readOff))); |
3436 | 0 | } |
3437 | 0 | else |
3438 | 0 | ereport(emode_for_corrupt_record(emode, targetPagePtr + reqLen), |
3439 | 0 | (errcode(ERRCODE_DATA_CORRUPTED), |
3440 | 0 | errmsg("could not read from WAL segment %s, LSN %X/%08X, offset %u: read %zd of %zu", |
3441 | 0 | fname, LSN_FORMAT_ARGS(targetPagePtr), |
3442 | 0 | readOff, r, (Size) XLOG_BLCKSZ))); |
3443 | 0 | goto next_record_is_invalid; |
3444 | 0 | } |
3445 | 0 | pgstat_report_wait_end(); |
3446 | |
|
3447 | 0 | pgstat_count_io_op_time(IOOBJECT_WAL, IOCONTEXT_NORMAL, IOOP_READ, |
3448 | 0 | io_start, 1, r); |
3449 | |
|
3450 | 0 | Assert(targetSegNo == readSegNo); |
3451 | 0 | Assert(targetPageOff == readOff); |
3452 | 0 | Assert(reqLen <= readLen); |
3453 | |
|
3454 | 0 | xlogreader->seg.ws_tli = curFileTLI; |
3455 | | |
3456 | | /* |
3457 | | * Check the page header immediately, so that we can retry immediately if |
3458 | | * it's not valid. This may seem unnecessary, because ReadPageInternal() |
3459 | | * validates the page header anyway, and would propagate the failure up to |
3460 | | * ReadRecord(), which would retry. However, there's a corner case with |
3461 | | * continuation records, if a record is split across two pages such that |
3462 | | * we would need to read the two pages from different sources across two |
3463 | | * WAL segments. |
3464 | | * |
3465 | | * The first page is only available locally, in pg_wal, because it's |
3466 | | * already been recycled on the primary. The second page, however, is not |
3467 | | * present in pg_wal, and we should stream it from the primary. There is a |
3468 | | * recycled WAL segment present in pg_wal, with garbage contents, however. |
3469 | | * We would read the first page from the local WAL segment, but when |
3470 | | * reading the second page, we would read the bogus, recycled, WAL |
3471 | | * segment. If we didn't catch that case here, we would never recover, |
3472 | | * because ReadRecord() would retry reading the whole record from the |
3473 | | * beginning. |
3474 | | * |
3475 | | * Of course, this only catches errors in the page header, which is what |
3476 | | * happens in the case of a recycled WAL segment. Other kinds of errors or |
3477 | | * corruption still has the same problem. But this at least fixes the |
3478 | | * common case, which can happen as part of normal operation. |
3479 | | * |
3480 | | * Validating the page header is cheap enough that doing it twice |
3481 | | * shouldn't be a big deal from a performance point of view. |
3482 | | * |
3483 | | * When not in standby mode, an invalid page header should cause recovery |
3484 | | * to end, not retry reading the page, so we don't need to validate the |
3485 | | * page header here for the retry. Instead, ReadPageInternal() is |
3486 | | * responsible for the validation. |
3487 | | */ |
3488 | 0 | if (StandbyMode && |
3489 | 0 | (targetPagePtr % wal_segment_size) == 0 && |
3490 | 0 | !XLogReaderValidatePageHeader(xlogreader, targetPagePtr, readBuf)) |
3491 | 0 | { |
3492 | | /* |
3493 | | * Emit this error right now then retry this page immediately. Use |
3494 | | * errmsg_internal() because the message was already translated. |
3495 | | */ |
3496 | 0 | if (xlogreader->errormsg_buf[0]) |
3497 | 0 | ereport(emode_for_corrupt_record(emode, xlogreader->EndRecPtr), |
3498 | 0 | (errmsg_internal("%s", xlogreader->errormsg_buf))); |
3499 | | |
3500 | | /* reset any error XLogReaderValidatePageHeader() might have set */ |
3501 | 0 | XLogReaderResetError(xlogreader); |
3502 | 0 | goto next_record_is_invalid; |
3503 | 0 | } |
3504 | | |
3505 | 0 | return readLen; |
3506 | | |
3507 | 0 | next_record_is_invalid: |
3508 | | |
3509 | | /* |
3510 | | * If we're reading ahead, give up fast. Retries and error reporting will |
3511 | | * be handled by a later read when recovery catches up to this point. |
3512 | | */ |
3513 | 0 | if (xlogreader->nonblocking) |
3514 | 0 | return XLREAD_WOULDBLOCK; |
3515 | | |
3516 | 0 | lastSourceFailed = true; |
3517 | |
|
3518 | 0 | if (readFile >= 0) |
3519 | 0 | close(readFile); |
3520 | 0 | readFile = -1; |
3521 | 0 | readLen = 0; |
3522 | 0 | readSource = XLOG_FROM_ANY; |
3523 | | |
3524 | | /* In standby-mode, keep trying */ |
3525 | 0 | if (StandbyMode) |
3526 | 0 | goto retry; |
3527 | 0 | else |
3528 | 0 | return XLREAD_FAIL; |
3529 | 0 | } |
3530 | | |
3531 | | /* |
3532 | | * Open the WAL segment containing WAL location 'RecPtr'. |
3533 | | * |
3534 | | * The segment can be fetched via restore_command, or via walreceiver having |
3535 | | * streamed the record, or it can already be present in pg_wal. Checking |
3536 | | * pg_wal is mainly for crash recovery, but it will be polled in standby mode |
3537 | | * too, in case someone copies a new segment directly to pg_wal. That is not |
3538 | | * documented or recommended, though. |
3539 | | * |
3540 | | * If 'fetching_ckpt' is true, we're fetching a checkpoint record, and should |
3541 | | * prepare to read WAL starting from RedoStartLSN after this. |
3542 | | * |
3543 | | * 'RecPtr' might not point to the beginning of the record we're interested |
3544 | | * in, it might also point to the page or segment header. In that case, |
3545 | | * 'tliRecPtr' is the position of the WAL record we're interested in. It is |
3546 | | * used to decide which timeline to stream the requested WAL from. |
3547 | | * |
3548 | | * 'replayLSN' is the current replay LSN, so that if we scan for new |
3549 | | * timelines, we can reject a switch to a timeline that branched off before |
3550 | | * this point. |
3551 | | * |
3552 | | * If the record is not immediately available, the function returns XLREAD_FAIL |
3553 | | * if we're not in standby mode. In standby mode, the function waits for it to |
3554 | | * become available. |
3555 | | * |
3556 | | * When the requested record becomes available, the function opens the file |
3557 | | * containing it (if not open already), and returns XLREAD_SUCCESS. When end |
3558 | | * of standby mode is triggered by the user, and there is no more WAL |
3559 | | * available, returns XLREAD_FAIL. |
3560 | | * |
3561 | | * If nonblocking is true, then give up immediately if we can't satisfy the |
3562 | | * request, returning XLREAD_WOULDBLOCK instead of waiting. |
3563 | | */ |
3564 | | static XLogPageReadResult |
3565 | | WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, |
3566 | | bool fetching_ckpt, XLogRecPtr tliRecPtr, |
3567 | | TimeLineID replayTLI, XLogRecPtr replayLSN, |
3568 | | bool nonblocking) |
3569 | 0 | { |
3570 | 0 | static TimestampTz last_fail_time = 0; |
3571 | 0 | TimestampTz now; |
3572 | 0 | bool streaming_reply_sent = false; |
3573 | | |
3574 | | /*------- |
3575 | | * Standby mode is implemented by a state machine: |
3576 | | * |
3577 | | * 1. Read from either archive or pg_wal (XLOG_FROM_ARCHIVE), or just |
3578 | | * pg_wal (XLOG_FROM_PG_WAL) |
3579 | | * 2. Check for promotion trigger request |
3580 | | * 3. Read from primary server via walreceiver (XLOG_FROM_STREAM) |
3581 | | * 4. Rescan timelines |
3582 | | * 5. Sleep wal_retrieve_retry_interval milliseconds, and loop back to 1. |
3583 | | * |
3584 | | * Failure to read from the current source advances the state machine to |
3585 | | * the next state. |
3586 | | * |
3587 | | * 'currentSource' indicates the current state. There are no currentSource |
3588 | | * values for "check trigger", "rescan timelines", and "sleep" states, |
3589 | | * those actions are taken when reading from the previous source fails, as |
3590 | | * part of advancing to the next state. |
3591 | | * |
3592 | | * If standby mode is turned off while reading WAL from stream, we move |
3593 | | * to XLOG_FROM_ARCHIVE and reset lastSourceFailed, to force fetching |
3594 | | * the files (which would be required at end of recovery, e.g., timeline |
3595 | | * history file) from archive or pg_wal. We don't need to kill WAL receiver |
3596 | | * here because it's already stopped when standby mode is turned off at |
3597 | | * the end of recovery. |
3598 | | *------- |
3599 | | */ |
3600 | 0 | if (!InArchiveRecovery) |
3601 | 0 | currentSource = XLOG_FROM_PG_WAL; |
3602 | 0 | else if (currentSource == XLOG_FROM_ANY || |
3603 | 0 | (!StandbyMode && currentSource == XLOG_FROM_STREAM)) |
3604 | 0 | { |
3605 | 0 | lastSourceFailed = false; |
3606 | 0 | currentSource = XLOG_FROM_ARCHIVE; |
3607 | 0 | } |
3608 | |
|
3609 | 0 | for (;;) |
3610 | 0 | { |
3611 | 0 | XLogSource oldSource = currentSource; |
3612 | 0 | bool startWalReceiver = false; |
3613 | | |
3614 | | /* |
3615 | | * First check if we failed to read from the current source, and |
3616 | | * advance the state machine if so. The failure to read might've |
3617 | | * happened outside this function, e.g when a CRC check fails on a |
3618 | | * record, or within this loop. |
3619 | | */ |
3620 | 0 | if (lastSourceFailed) |
3621 | 0 | { |
3622 | | /* |
3623 | | * Don't allow any retry loops to occur during nonblocking |
3624 | | * readahead. Let the caller process everything that has been |
3625 | | * decoded already first. |
3626 | | */ |
3627 | 0 | if (nonblocking) |
3628 | 0 | return XLREAD_WOULDBLOCK; |
3629 | | |
3630 | 0 | switch (currentSource) |
3631 | 0 | { |
3632 | 0 | case XLOG_FROM_ARCHIVE: |
3633 | 0 | case XLOG_FROM_PG_WAL: |
3634 | | |
3635 | | /* |
3636 | | * Check to see if promotion is requested. Note that we do |
3637 | | * this only after failure, so when you promote, we still |
3638 | | * finish replaying as much as we can from archive and |
3639 | | * pg_wal before failover. |
3640 | | */ |
3641 | 0 | if (StandbyMode && CheckForStandbyTrigger()) |
3642 | 0 | { |
3643 | 0 | XLogShutdownWalRcv(); |
3644 | 0 | return XLREAD_FAIL; |
3645 | 0 | } |
3646 | | |
3647 | | /* |
3648 | | * Not in standby mode, and we've now tried the archive |
3649 | | * and pg_wal. |
3650 | | */ |
3651 | 0 | if (!StandbyMode) |
3652 | 0 | return XLREAD_FAIL; |
3653 | | |
3654 | | /* |
3655 | | * Move to XLOG_FROM_STREAM state, and set to start a |
3656 | | * walreceiver if necessary. |
3657 | | */ |
3658 | 0 | currentSource = XLOG_FROM_STREAM; |
3659 | 0 | startWalReceiver = true; |
3660 | 0 | break; |
3661 | | |
3662 | 0 | case XLOG_FROM_STREAM: |
3663 | | |
3664 | | /* |
3665 | | * Failure while streaming. Most likely, we got here |
3666 | | * because streaming replication was terminated, or |
3667 | | * promotion was triggered. But we also get here if we |
3668 | | * find an invalid record in the WAL streamed from the |
3669 | | * primary, in which case something is seriously wrong. |
3670 | | * There's little chance that the problem will just go |
3671 | | * away, but PANIC is not good for availability either, |
3672 | | * especially in hot standby mode. So, we treat that the |
3673 | | * same as disconnection, and retry from archive/pg_wal |
3674 | | * again. The WAL in the archive should be identical to |
3675 | | * what was streamed, so it's unlikely that it helps, but |
3676 | | * one can hope... |
3677 | | */ |
3678 | | |
3679 | | /* |
3680 | | * We should be able to move to XLOG_FROM_STREAM only in |
3681 | | * standby mode. |
3682 | | */ |
3683 | 0 | Assert(StandbyMode); |
3684 | | |
3685 | | /* |
3686 | | * Before we leave XLOG_FROM_STREAM state, make sure that |
3687 | | * walreceiver is not active, so that it won't overwrite |
3688 | | * WAL that we restore from archive. |
3689 | | * |
3690 | | * If walreceiver is actively streaming (or attempting to |
3691 | | * connect), we must shut it down. However, if it's |
3692 | | * already in WAITING state (e.g., due to timeline |
3693 | | * divergence), we only need to reset the install flag to |
3694 | | * allow archive restoration. |
3695 | | */ |
3696 | 0 | if (WalRcvStreaming()) |
3697 | 0 | XLogShutdownWalRcv(); |
3698 | 0 | else |
3699 | 0 | { |
3700 | | /* |
3701 | | * WALRCV_STOPPING state is a transient state while |
3702 | | * the startup process is in ShutdownWalRcv(). It |
3703 | | * should never appear here since we would be waiting |
3704 | | * for the walreceiver to reach WALRCV_STOPPED in that |
3705 | | * case. |
3706 | | */ |
3707 | 0 | Assert(WalRcvGetState() != WALRCV_STOPPING); |
3708 | 0 | ResetInstallXLogFileSegmentActive(); |
3709 | 0 | } |
3710 | | |
3711 | | /* |
3712 | | * Before we sleep, re-scan for possible new timelines if |
3713 | | * we were requested to recover to the latest timeline. |
3714 | | */ |
3715 | 0 | if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST) |
3716 | 0 | { |
3717 | 0 | if (rescanLatestTimeLine(replayTLI, replayLSN)) |
3718 | 0 | { |
3719 | 0 | currentSource = XLOG_FROM_ARCHIVE; |
3720 | 0 | break; |
3721 | 0 | } |
3722 | 0 | } |
3723 | | |
3724 | | /* |
3725 | | * XLOG_FROM_STREAM is the last state in our state |
3726 | | * machine, so we've exhausted all the options for |
3727 | | * obtaining the requested WAL. We're going to loop back |
3728 | | * and retry from the archive, but if it hasn't been long |
3729 | | * since last attempt, sleep wal_retrieve_retry_interval |
3730 | | * milliseconds to avoid busy-waiting. |
3731 | | */ |
3732 | 0 | now = GetCurrentTimestamp(); |
3733 | 0 | if (!TimestampDifferenceExceeds(last_fail_time, now, |
3734 | 0 | wal_retrieve_retry_interval)) |
3735 | 0 | { |
3736 | 0 | long wait_time; |
3737 | |
|
3738 | 0 | wait_time = wal_retrieve_retry_interval - |
3739 | 0 | TimestampDifferenceMilliseconds(last_fail_time, now); |
3740 | |
|
3741 | 0 | elog(LOG, "waiting for WAL to become available at %X/%08X", |
3742 | 0 | LSN_FORMAT_ARGS(RecPtr)); |
3743 | | |
3744 | | /* Do background tasks that might benefit us later. */ |
3745 | 0 | KnownAssignedTransactionIdsIdleMaintenance(); |
3746 | |
|
3747 | 0 | (void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch, |
3748 | 0 | WL_LATCH_SET | WL_TIMEOUT | |
3749 | 0 | WL_EXIT_ON_PM_DEATH, |
3750 | 0 | wait_time, |
3751 | 0 | WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL); |
3752 | 0 | ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
3753 | 0 | now = GetCurrentTimestamp(); |
3754 | | |
3755 | | /* Handle interrupt signals of startup process */ |
3756 | 0 | ProcessStartupProcInterrupts(); |
3757 | 0 | } |
3758 | 0 | last_fail_time = now; |
3759 | 0 | currentSource = XLOG_FROM_ARCHIVE; |
3760 | 0 | break; |
3761 | | |
3762 | 0 | default: |
3763 | 0 | elog(ERROR, "unexpected WAL source %d", currentSource); |
3764 | 0 | } |
3765 | 0 | } |
3766 | 0 | else if (currentSource == XLOG_FROM_PG_WAL) |
3767 | 0 | { |
3768 | | /* |
3769 | | * We just successfully read a file in pg_wal. We prefer files in |
3770 | | * the archive over ones in pg_wal, so try the next file again |
3771 | | * from the archive first. |
3772 | | */ |
3773 | 0 | if (InArchiveRecovery) |
3774 | 0 | currentSource = XLOG_FROM_ARCHIVE; |
3775 | 0 | } |
3776 | | |
3777 | 0 | if (currentSource != oldSource) |
3778 | 0 | elog(DEBUG2, "switched WAL source from %s to %s after %s", |
3779 | 0 | xlogSourceNames[oldSource], xlogSourceNames[currentSource], |
3780 | 0 | lastSourceFailed ? "failure" : "success"); |
3781 | | |
3782 | | /* |
3783 | | * We've now handled possible failure. Try to read from the chosen |
3784 | | * source. |
3785 | | */ |
3786 | 0 | lastSourceFailed = false; |
3787 | |
|
3788 | 0 | switch (currentSource) |
3789 | 0 | { |
3790 | 0 | case XLOG_FROM_ARCHIVE: |
3791 | 0 | case XLOG_FROM_PG_WAL: |
3792 | | |
3793 | | /* |
3794 | | * WAL receiver must not be running when reading WAL from |
3795 | | * archive or pg_wal. |
3796 | | */ |
3797 | 0 | Assert(!WalRcvStreaming()); |
3798 | | |
3799 | | /* Close any old file we might have open. */ |
3800 | 0 | if (readFile >= 0) |
3801 | 0 | { |
3802 | 0 | close(readFile); |
3803 | 0 | readFile = -1; |
3804 | 0 | } |
3805 | | /* Reset curFileTLI if random fetch. */ |
3806 | 0 | if (randAccess) |
3807 | 0 | curFileTLI = 0; |
3808 | | |
3809 | | /* |
3810 | | * Try to restore the file from archive, or read an existing |
3811 | | * file from pg_wal. |
3812 | | */ |
3813 | 0 | readFile = XLogFileReadAnyTLI(readSegNo, |
3814 | 0 | currentSource == XLOG_FROM_ARCHIVE ? XLOG_FROM_ANY : |
3815 | 0 | currentSource); |
3816 | 0 | if (readFile >= 0) |
3817 | 0 | return XLREAD_SUCCESS; /* success! */ |
3818 | | |
3819 | | /* |
3820 | | * Nope, not found in archive or pg_wal. |
3821 | | */ |
3822 | 0 | lastSourceFailed = true; |
3823 | 0 | break; |
3824 | | |
3825 | 0 | case XLOG_FROM_STREAM: |
3826 | 0 | { |
3827 | 0 | bool havedata; |
3828 | | |
3829 | | /* |
3830 | | * We should be able to move to XLOG_FROM_STREAM only in |
3831 | | * standby mode. |
3832 | | */ |
3833 | 0 | Assert(StandbyMode); |
3834 | | |
3835 | | /* |
3836 | | * First, shutdown walreceiver if its restart has been |
3837 | | * requested -- but no point if we're already slated for |
3838 | | * starting it. |
3839 | | */ |
3840 | 0 | if (pendingWalRcvRestart && !startWalReceiver) |
3841 | 0 | { |
3842 | 0 | XLogShutdownWalRcv(); |
3843 | | |
3844 | | /* |
3845 | | * Re-scan for possible new timelines if we were |
3846 | | * requested to recover to the latest timeline. |
3847 | | */ |
3848 | 0 | if (recoveryTargetTimeLineGoal == |
3849 | 0 | RECOVERY_TARGET_TIMELINE_LATEST) |
3850 | 0 | rescanLatestTimeLine(replayTLI, replayLSN); |
3851 | |
|
3852 | 0 | startWalReceiver = true; |
3853 | 0 | } |
3854 | 0 | pendingWalRcvRestart = false; |
3855 | | |
3856 | | /* |
3857 | | * Launch walreceiver if needed. |
3858 | | * |
3859 | | * If fetching_ckpt is true, RecPtr points to the initial |
3860 | | * checkpoint location. In that case, we use RedoStartLSN |
3861 | | * as the streaming start position instead of RecPtr, so |
3862 | | * that when we later jump backwards to start redo at |
3863 | | * RedoStartLSN, we will have the logs streamed already. |
3864 | | */ |
3865 | 0 | if (startWalReceiver && |
3866 | 0 | PrimaryConnInfo && strcmp(PrimaryConnInfo, "") != 0) |
3867 | 0 | { |
3868 | 0 | XLogRecPtr ptr; |
3869 | 0 | TimeLineID tli; |
3870 | |
|
3871 | 0 | if (fetching_ckpt) |
3872 | 0 | { |
3873 | 0 | ptr = RedoStartLSN; |
3874 | 0 | tli = RedoStartTLI; |
3875 | 0 | } |
3876 | 0 | else |
3877 | 0 | { |
3878 | 0 | ptr = RecPtr; |
3879 | | |
3880 | | /* |
3881 | | * Use the record begin position to determine the |
3882 | | * TLI, rather than the position we're reading. |
3883 | | */ |
3884 | 0 | tli = tliOfPointInHistory(tliRecPtr, expectedTLEs); |
3885 | |
|
3886 | 0 | if (curFileTLI > 0 && tli < curFileTLI) |
3887 | 0 | elog(ERROR, "according to history file, WAL location %X/%08X belongs to timeline %u, but previous recovered WAL file came from timeline %u", |
3888 | 0 | LSN_FORMAT_ARGS(tliRecPtr), |
3889 | 0 | tli, curFileTLI); |
3890 | 0 | } |
3891 | 0 | curFileTLI = tli; |
3892 | 0 | SetInstallXLogFileSegmentActive(); |
3893 | 0 | RequestXLogStreaming(tli, ptr, PrimaryConnInfo, |
3894 | 0 | PrimarySlotName, |
3895 | 0 | wal_receiver_create_temp_slot); |
3896 | 0 | flushedUpto = InvalidXLogRecPtr; |
3897 | 0 | } |
3898 | | |
3899 | | /* |
3900 | | * Check if WAL receiver is active or wait to start up. |
3901 | | */ |
3902 | 0 | if (!WalRcvStreaming()) |
3903 | 0 | { |
3904 | 0 | lastSourceFailed = true; |
3905 | 0 | break; |
3906 | 0 | } |
3907 | | |
3908 | | /* |
3909 | | * Walreceiver is active, so see if new data has arrived. |
3910 | | * |
3911 | | * We only advance XLogReceiptTime when we obtain fresh |
3912 | | * WAL from walreceiver and observe that we had already |
3913 | | * processed everything before the most recent "chunk" |
3914 | | * that it flushed to disk. In steady state where we are |
3915 | | * keeping up with the incoming data, XLogReceiptTime will |
3916 | | * be updated on each cycle. When we are behind, |
3917 | | * XLogReceiptTime will not advance, so the grace time |
3918 | | * allotted to conflicting queries will decrease. |
3919 | | */ |
3920 | 0 | if (RecPtr < flushedUpto) |
3921 | 0 | havedata = true; |
3922 | 0 | else |
3923 | 0 | { |
3924 | 0 | XLogRecPtr latestChunkStart; |
3925 | |
|
3926 | 0 | flushedUpto = GetWalRcvFlushRecPtr(&latestChunkStart, &receiveTLI); |
3927 | 0 | if (RecPtr < flushedUpto && receiveTLI == curFileTLI) |
3928 | 0 | { |
3929 | 0 | havedata = true; |
3930 | 0 | if (latestChunkStart <= RecPtr) |
3931 | 0 | { |
3932 | 0 | XLogReceiptTime = GetCurrentTimestamp(); |
3933 | 0 | SetCurrentChunkStartTime(XLogReceiptTime); |
3934 | 0 | } |
3935 | 0 | } |
3936 | 0 | else |
3937 | 0 | havedata = false; |
3938 | 0 | } |
3939 | 0 | if (havedata) |
3940 | 0 | { |
3941 | | /* |
3942 | | * Great, streamed far enough. Open the file if it's |
3943 | | * not open already. Also read the timeline history |
3944 | | * file if we haven't initialized timeline history |
3945 | | * yet; it should be streamed over and present in |
3946 | | * pg_wal by now. Use XLOG_FROM_STREAM so that source |
3947 | | * info is set correctly and XLogReceiptTime isn't |
3948 | | * changed. |
3949 | | * |
3950 | | * NB: We must set readTimeLineHistory based on |
3951 | | * recoveryTargetTLI, not receiveTLI. Normally they'll |
3952 | | * be the same, but if recovery_target_timeline is |
3953 | | * 'latest' and archiving is configured, then it's |
3954 | | * possible that we managed to retrieve one or more |
3955 | | * new timeline history files from the archive, |
3956 | | * updating recoveryTargetTLI. |
3957 | | */ |
3958 | 0 | if (readFile < 0) |
3959 | 0 | { |
3960 | 0 | if (!expectedTLEs) |
3961 | 0 | expectedTLEs = readTimeLineHistory(recoveryTargetTLI); |
3962 | 0 | readFile = XLogFileRead(readSegNo, receiveTLI, |
3963 | 0 | XLOG_FROM_STREAM, false); |
3964 | 0 | Assert(readFile >= 0); |
3965 | 0 | } |
3966 | 0 | else |
3967 | 0 | { |
3968 | | /* just make sure source info is correct... */ |
3969 | 0 | readSource = XLOG_FROM_STREAM; |
3970 | 0 | XLogReceiptSource = XLOG_FROM_STREAM; |
3971 | 0 | return XLREAD_SUCCESS; |
3972 | 0 | } |
3973 | 0 | break; |
3974 | 0 | } |
3975 | | |
3976 | | /* In nonblocking mode, return rather than sleeping. */ |
3977 | 0 | if (nonblocking) |
3978 | 0 | return XLREAD_WOULDBLOCK; |
3979 | | |
3980 | | /* |
3981 | | * Data not here yet. Check for trigger, then wait for |
3982 | | * walreceiver to wake us up when new WAL arrives. |
3983 | | */ |
3984 | 0 | if (CheckForStandbyTrigger()) |
3985 | 0 | { |
3986 | | /* |
3987 | | * Note that we don't return XLREAD_FAIL immediately |
3988 | | * here. After being triggered, we still want to |
3989 | | * replay all the WAL that was already streamed. It's |
3990 | | * in pg_wal now, so we just treat this as a failure, |
3991 | | * and the state machine will move on to replay the |
3992 | | * streamed WAL from pg_wal, and then recheck the |
3993 | | * trigger and exit replay. |
3994 | | */ |
3995 | 0 | lastSourceFailed = true; |
3996 | 0 | break; |
3997 | 0 | } |
3998 | | |
3999 | | /* |
4000 | | * Since we have replayed everything we have received so |
4001 | | * far and are about to start waiting for more WAL, let's |
4002 | | * tell the upstream server our replay location now so |
4003 | | * that pg_stat_replication doesn't show stale |
4004 | | * information. |
4005 | | */ |
4006 | 0 | if (!streaming_reply_sent) |
4007 | 0 | { |
4008 | 0 | WalRcvRequestApplyReply(); |
4009 | 0 | streaming_reply_sent = true; |
4010 | 0 | } |
4011 | | |
4012 | | /* Do any background tasks that might benefit us later. */ |
4013 | 0 | KnownAssignedTransactionIdsIdleMaintenance(); |
4014 | | |
4015 | | /* Update pg_stat_recovery_prefetch before sleeping. */ |
4016 | 0 | XLogPrefetcherComputeStats(xlogprefetcher); |
4017 | | |
4018 | | /* |
4019 | | * Wait for more WAL to arrive, when we will be woken |
4020 | | * immediately by the WAL receiver. |
4021 | | */ |
4022 | 0 | (void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch, |
4023 | 0 | WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, |
4024 | 0 | -1L, |
4025 | 0 | WAIT_EVENT_RECOVERY_WAL_STREAM); |
4026 | 0 | ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
4027 | 0 | break; |
4028 | 0 | } |
4029 | | |
4030 | 0 | default: |
4031 | 0 | elog(ERROR, "unexpected WAL source %d", currentSource); |
4032 | 0 | } |
4033 | | |
4034 | | /* |
4035 | | * Check for recovery pause here so that we can confirm more quickly |
4036 | | * that a requested pause has actually taken effect. |
4037 | | */ |
4038 | 0 | if (((volatile XLogRecoveryCtlData *) XLogRecoveryCtl)->recoveryPauseState != |
4039 | 0 | RECOVERY_NOT_PAUSED) |
4040 | 0 | recoveryPausesHere(false); |
4041 | | |
4042 | | /* |
4043 | | * This possibly-long loop needs to handle interrupts of startup |
4044 | | * process. |
4045 | | */ |
4046 | 0 | ProcessStartupProcInterrupts(); |
4047 | 0 | } |
4048 | | |
4049 | 0 | return XLREAD_FAIL; /* not reached */ |
4050 | 0 | } |
4051 | | |
4052 | | |
4053 | | /* |
4054 | | * Determine what log level should be used to report a corrupt WAL record |
4055 | | * in the current WAL page, previously read by XLogPageRead(). |
4056 | | * |
4057 | | * 'emode' is the error mode that would be used to report a file-not-found |
4058 | | * or legitimate end-of-WAL situation. Generally, we use it as-is, but if |
4059 | | * we're retrying the exact same record that we've tried previously, only |
4060 | | * complain the first time to keep the noise down. However, we only do when |
4061 | | * reading from pg_wal, because we don't expect any invalid records in archive |
4062 | | * or in records streamed from the primary. Files in the archive should be complete, |
4063 | | * and we should never hit the end of WAL because we stop and wait for more WAL |
4064 | | * to arrive before replaying it. |
4065 | | * |
4066 | | * NOTE: This function remembers the RecPtr value it was last called with, |
4067 | | * to suppress repeated messages about the same record. Only call this when |
4068 | | * you are about to ereport(), or you might cause a later message to be |
4069 | | * erroneously suppressed. |
4070 | | */ |
4071 | | static int |
4072 | | emode_for_corrupt_record(int emode, XLogRecPtr RecPtr) |
4073 | 0 | { |
4074 | 0 | static XLogRecPtr lastComplaint = InvalidXLogRecPtr; |
4075 | |
|
4076 | 0 | if (readSource == XLOG_FROM_PG_WAL && emode == LOG) |
4077 | 0 | { |
4078 | 0 | if (RecPtr == lastComplaint) |
4079 | 0 | emode = DEBUG1; |
4080 | 0 | else |
4081 | 0 | lastComplaint = RecPtr; |
4082 | 0 | } |
4083 | 0 | return emode; |
4084 | 0 | } |
4085 | | |
4086 | | |
4087 | | /* |
4088 | | * Subroutine to try to fetch and validate a prior checkpoint record. |
4089 | | */ |
4090 | | static XLogRecord * |
4091 | | ReadCheckpointRecord(XLogPrefetcher *xlogprefetcher, XLogRecPtr RecPtr, |
4092 | | TimeLineID replayTLI) |
4093 | | { |
4094 | | XLogRecord *record; |
4095 | | uint8 info; |
4096 | | |
4097 | | Assert(xlogreader != NULL); |
4098 | | |
4099 | | if (!XRecOffIsValid(RecPtr)) |
4100 | | { |
4101 | | ereport(LOG, |
4102 | | (errmsg("invalid checkpoint location"))); |
4103 | | return NULL; |
4104 | | } |
4105 | | |
4106 | | XLogPrefetcherBeginRead(xlogprefetcher, RecPtr); |
4107 | | record = ReadRecord(xlogprefetcher, LOG, true, replayTLI); |
4108 | | |
4109 | | if (record == NULL) |
4110 | | { |
4111 | | ereport(LOG, |
4112 | | (errmsg("invalid checkpoint record"))); |
4113 | | return NULL; |
4114 | | } |
4115 | | if (record->xl_rmid != RM_XLOG_ID) |
4116 | | { |
4117 | | ereport(LOG, |
4118 | | (errmsg("invalid resource manager ID in checkpoint record"))); |
4119 | | return NULL; |
4120 | | } |
4121 | | info = record->xl_info & ~XLR_INFO_MASK; |
4122 | | if (info != XLOG_CHECKPOINT_SHUTDOWN && |
4123 | | info != XLOG_CHECKPOINT_ONLINE) |
4124 | | { |
4125 | | ereport(LOG, |
4126 | | (errmsg("invalid xl_info in checkpoint record"))); |
4127 | | return NULL; |
4128 | | } |
4129 | | if (record->xl_tot_len != SizeOfXLogRecord + SizeOfXLogRecordDataHeaderShort + sizeof(CheckPoint)) |
4130 | | { |
4131 | | ereport(LOG, |
4132 | | (errmsg("invalid length of checkpoint record"))); |
4133 | | return NULL; |
4134 | | } |
4135 | | return record; |
4136 | | } |
4137 | | |
4138 | | /* |
4139 | | * Scan for new timelines that might have appeared in the archive since we |
4140 | | * started recovery. |
4141 | | * |
4142 | | * If there are any, the function changes recovery target TLI to the latest |
4143 | | * one and returns 'true'. |
4144 | | */ |
4145 | | static bool |
4146 | | rescanLatestTimeLine(TimeLineID replayTLI, XLogRecPtr replayLSN) |
4147 | | { |
4148 | | List *newExpectedTLEs; |
4149 | | bool found; |
4150 | | ListCell *cell; |
4151 | | TimeLineID newtarget; |
4152 | | TimeLineID oldtarget = recoveryTargetTLI; |
4153 | | TimeLineHistoryEntry *currentTle = NULL; |
4154 | | |
4155 | | newtarget = findNewestTimeLine(recoveryTargetTLI); |
4156 | | if (newtarget == recoveryTargetTLI) |
4157 | | { |
4158 | | /* No new timelines found */ |
4159 | | return false; |
4160 | | } |
4161 | | |
4162 | | /* |
4163 | | * Determine the list of expected TLIs for the new TLI |
4164 | | */ |
4165 | | |
4166 | | newExpectedTLEs = readTimeLineHistory(newtarget); |
4167 | | |
4168 | | /* |
4169 | | * If the current timeline is not part of the history of the new timeline, |
4170 | | * we cannot proceed to it. |
4171 | | */ |
4172 | | found = false; |
4173 | | foreach(cell, newExpectedTLEs) |
4174 | | { |
4175 | | currentTle = (TimeLineHistoryEntry *) lfirst(cell); |
4176 | | |
4177 | | if (currentTle->tli == recoveryTargetTLI) |
4178 | | { |
4179 | | found = true; |
4180 | | break; |
4181 | | } |
4182 | | } |
4183 | | if (!found) |
4184 | | { |
4185 | | ereport(LOG, |
4186 | | (errmsg("new timeline %u is not a child of database system timeline %u", |
4187 | | newtarget, |
4188 | | replayTLI))); |
4189 | | return false; |
4190 | | } |
4191 | | |
4192 | | /* |
4193 | | * The current timeline was found in the history file, but check that the |
4194 | | * next timeline was forked off from it *after* the current recovery |
4195 | | * location. |
4196 | | */ |
4197 | | if (currentTle->end < replayLSN) |
4198 | | { |
4199 | | ereport(LOG, |
4200 | | errmsg("new timeline %u forked off current database system timeline %u before current recovery point %X/%08X", |
4201 | | newtarget, |
4202 | | replayTLI, |
4203 | | LSN_FORMAT_ARGS(replayLSN))); |
4204 | | return false; |
4205 | | } |
4206 | | |
4207 | | /* The new timeline history seems valid. Switch target */ |
4208 | | recoveryTargetTLI = newtarget; |
4209 | | list_free_deep(expectedTLEs); |
4210 | | expectedTLEs = newExpectedTLEs; |
4211 | | |
4212 | | /* |
4213 | | * As in StartupXLOG(), try to ensure we have all the history files |
4214 | | * between the old target and new target in pg_wal. |
4215 | | */ |
4216 | | restoreTimeLineHistoryFiles(oldtarget + 1, newtarget); |
4217 | | |
4218 | | ereport(LOG, |
4219 | | (errmsg("new target timeline is %u", |
4220 | | recoveryTargetTLI))); |
4221 | | |
4222 | | return true; |
4223 | | } |
4224 | | |
4225 | | |
4226 | | /* |
4227 | | * Open a logfile segment for reading (during recovery). |
4228 | | * |
4229 | | * If source == XLOG_FROM_ARCHIVE, the segment is retrieved from archive. |
4230 | | * Otherwise, it's assumed to be already available in pg_wal. |
4231 | | */ |
4232 | | static int |
4233 | | XLogFileRead(XLogSegNo segno, TimeLineID tli, |
4234 | | XLogSource source, bool notfoundOk) |
4235 | 0 | { |
4236 | 0 | char xlogfname[MAXFNAMELEN]; |
4237 | 0 | char activitymsg[MAXFNAMELEN + 16]; |
4238 | 0 | char path[MAXPGPATH]; |
4239 | 0 | int fd; |
4240 | |
|
4241 | 0 | XLogFileName(xlogfname, tli, segno, wal_segment_size); |
4242 | |
|
4243 | 0 | switch (source) |
4244 | 0 | { |
4245 | 0 | case XLOG_FROM_ARCHIVE: |
4246 | | /* Report recovery progress in PS display */ |
4247 | 0 | snprintf(activitymsg, sizeof(activitymsg), "waiting for %s", |
4248 | 0 | xlogfname); |
4249 | 0 | set_ps_display(activitymsg); |
4250 | |
|
4251 | 0 | if (!RestoreArchivedFile(path, xlogfname, |
4252 | 0 | "RECOVERYXLOG", |
4253 | 0 | wal_segment_size, |
4254 | 0 | InRedo)) |
4255 | 0 | return -1; |
4256 | 0 | break; |
4257 | | |
4258 | 0 | case XLOG_FROM_PG_WAL: |
4259 | 0 | case XLOG_FROM_STREAM: |
4260 | 0 | XLogFilePath(path, tli, segno, wal_segment_size); |
4261 | 0 | break; |
4262 | | |
4263 | 0 | default: |
4264 | 0 | elog(ERROR, "invalid XLogFileRead source %d", source); |
4265 | 0 | } |
4266 | | |
4267 | | /* |
4268 | | * If the segment was fetched from archival storage, replace the existing |
4269 | | * xlog segment (if any) with the archival version. |
4270 | | */ |
4271 | 0 | if (source == XLOG_FROM_ARCHIVE) |
4272 | 0 | { |
4273 | 0 | Assert(!IsInstallXLogFileSegmentActive()); |
4274 | 0 | KeepFileRestoredFromArchive(path, xlogfname); |
4275 | | |
4276 | | /* |
4277 | | * Set path to point at the new file in pg_wal. |
4278 | | */ |
4279 | 0 | snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname); |
4280 | 0 | } |
4281 | |
|
4282 | 0 | fd = BasicOpenFile(path, O_RDONLY | PG_BINARY); |
4283 | 0 | if (fd >= 0) |
4284 | 0 | { |
4285 | | /* Success! */ |
4286 | 0 | curFileTLI = tli; |
4287 | | |
4288 | | /* Report recovery progress in PS display */ |
4289 | 0 | snprintf(activitymsg, sizeof(activitymsg), "recovering %s", |
4290 | 0 | xlogfname); |
4291 | 0 | set_ps_display(activitymsg); |
4292 | | |
4293 | | /* Track source of data in assorted state variables */ |
4294 | 0 | readSource = source; |
4295 | 0 | XLogReceiptSource = source; |
4296 | | /* In FROM_STREAM case, caller tracks receipt time, not me */ |
4297 | 0 | if (source != XLOG_FROM_STREAM) |
4298 | 0 | XLogReceiptTime = GetCurrentTimestamp(); |
4299 | |
|
4300 | 0 | return fd; |
4301 | 0 | } |
4302 | 0 | if (errno != ENOENT || !notfoundOk) /* unexpected failure? */ |
4303 | 0 | ereport(PANIC, |
4304 | 0 | (errcode_for_file_access(), |
4305 | 0 | errmsg("could not open file \"%s\": %m", path))); |
4306 | 0 | return -1; |
4307 | 0 | } |
4308 | | |
4309 | | /* |
4310 | | * Open a logfile segment for reading (during recovery). |
4311 | | * |
4312 | | * This version searches for the segment with any TLI listed in expectedTLEs. |
4313 | | */ |
4314 | | static int |
4315 | | XLogFileReadAnyTLI(XLogSegNo segno, XLogSource source) |
4316 | | { |
4317 | | char path[MAXPGPATH]; |
4318 | | ListCell *cell; |
4319 | | int fd; |
4320 | | List *tles; |
4321 | | |
4322 | | /* |
4323 | | * Loop looking for a suitable timeline ID: we might need to read any of |
4324 | | * the timelines listed in expectedTLEs. |
4325 | | * |
4326 | | * We expect curFileTLI on entry to be the TLI of the preceding file in |
4327 | | * sequence, or 0 if there was no predecessor. We do not allow curFileTLI |
4328 | | * to go backwards; this prevents us from picking up the wrong file when a |
4329 | | * parent timeline extends to higher segment numbers than the child we |
4330 | | * want to read. |
4331 | | * |
4332 | | * If we haven't read the timeline history file yet, read it now, so that |
4333 | | * we know which TLIs to scan. We don't save the list in expectedTLEs, |
4334 | | * however, unless we actually find a valid segment. That way if there is |
4335 | | * neither a timeline history file nor a WAL segment in the archive, and |
4336 | | * streaming replication is set up, we'll read the timeline history file |
4337 | | * streamed from the primary when we start streaming, instead of |
4338 | | * recovering with a dummy history generated here. |
4339 | | */ |
4340 | | if (expectedTLEs) |
4341 | | tles = expectedTLEs; |
4342 | | else |
4343 | | tles = readTimeLineHistory(recoveryTargetTLI); |
4344 | | |
4345 | | foreach(cell, tles) |
4346 | | { |
4347 | | TimeLineHistoryEntry *hent = (TimeLineHistoryEntry *) lfirst(cell); |
4348 | | TimeLineID tli = hent->tli; |
4349 | | |
4350 | | if (tli < curFileTLI) |
4351 | | break; /* don't bother looking at too-old TLIs */ |
4352 | | |
4353 | | /* |
4354 | | * Skip scanning the timeline ID that the logfile segment to read |
4355 | | * doesn't belong to |
4356 | | */ |
4357 | | if (XLogRecPtrIsValid(hent->begin)) |
4358 | | { |
4359 | | XLogSegNo beginseg = 0; |
4360 | | |
4361 | | XLByteToSeg(hent->begin, beginseg, wal_segment_size); |
4362 | | |
4363 | | /* |
4364 | | * The logfile segment that doesn't belong to the timeline is |
4365 | | * older or newer than the segment that the timeline started or |
4366 | | * ended at, respectively. It's sufficient to check only the |
4367 | | * starting segment of the timeline here. Since the timelines are |
4368 | | * scanned in descending order in this loop, any segments newer |
4369 | | * than the ending segment should belong to newer timeline and |
4370 | | * have already been read before. So it's not necessary to check |
4371 | | * the ending segment of the timeline here. |
4372 | | */ |
4373 | | if (segno < beginseg) |
4374 | | continue; |
4375 | | } |
4376 | | |
4377 | | if (source == XLOG_FROM_ANY || source == XLOG_FROM_ARCHIVE) |
4378 | | { |
4379 | | fd = XLogFileRead(segno, tli, XLOG_FROM_ARCHIVE, true); |
4380 | | if (fd != -1) |
4381 | | { |
4382 | | elog(DEBUG1, "got WAL segment from archive"); |
4383 | | if (!expectedTLEs) |
4384 | | expectedTLEs = tles; |
4385 | | return fd; |
4386 | | } |
4387 | | } |
4388 | | |
4389 | | if (source == XLOG_FROM_ANY || source == XLOG_FROM_PG_WAL) |
4390 | | { |
4391 | | fd = XLogFileRead(segno, tli, XLOG_FROM_PG_WAL, true); |
4392 | | if (fd != -1) |
4393 | | { |
4394 | | if (!expectedTLEs) |
4395 | | expectedTLEs = tles; |
4396 | | return fd; |
4397 | | } |
4398 | | } |
4399 | | } |
4400 | | |
4401 | | /* Couldn't find it. For simplicity, complain about front timeline */ |
4402 | | XLogFilePath(path, recoveryTargetTLI, segno, wal_segment_size); |
4403 | | errno = ENOENT; |
4404 | | ereport(DEBUG2, |
4405 | | (errcode_for_file_access(), |
4406 | | errmsg("could not open file \"%s\": %m", path))); |
4407 | | return -1; |
4408 | | } |
4409 | | |
4410 | | /* |
4411 | | * Set flag to signal the walreceiver to restart. (The startup process calls |
4412 | | * this on noticing a relevant configuration change.) |
4413 | | */ |
4414 | | void |
4415 | | StartupRequestWalReceiverRestart(void) |
4416 | | { |
4417 | | if (currentSource == XLOG_FROM_STREAM && WalRcvRunning()) |
4418 | | { |
4419 | | ereport(LOG, |
4420 | | (errmsg("WAL receiver process shutdown requested"))); |
4421 | | |
4422 | | pendingWalRcvRestart = true; |
4423 | | } |
4424 | | } |
4425 | | |
4426 | | |
4427 | | /* |
4428 | | * Has a standby promotion already been triggered? |
4429 | | * |
4430 | | * Unlike CheckForStandbyTrigger(), this works in any process |
4431 | | * that's connected to shared memory. |
4432 | | */ |
4433 | | bool |
4434 | | PromoteIsTriggered(void) |
4435 | 0 | { |
4436 | | /* |
4437 | | * We check shared state each time only until a standby promotion is |
4438 | | * triggered. We can't trigger a promotion again, so there's no need to |
4439 | | * keep checking after the shared variable has once been seen true. |
4440 | | */ |
4441 | 0 | if (LocalPromoteIsTriggered) |
4442 | 0 | return true; |
4443 | | |
4444 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4445 | 0 | LocalPromoteIsTriggered = XLogRecoveryCtl->SharedPromoteIsTriggered; |
4446 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4447 | |
|
4448 | 0 | return LocalPromoteIsTriggered; |
4449 | 0 | } |
4450 | | |
4451 | | static void |
4452 | | SetPromoteIsTriggered(void) |
4453 | 0 | { |
4454 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4455 | 0 | XLogRecoveryCtl->SharedPromoteIsTriggered = true; |
4456 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4457 | | |
4458 | | /* |
4459 | | * Mark the recovery pause state as 'not paused' because the paused state |
4460 | | * ends and promotion continues if a promotion is triggered while recovery |
4461 | | * is paused. Otherwise pg_get_wal_replay_pause_state() can mistakenly |
4462 | | * return 'paused' while a promotion is ongoing. |
4463 | | */ |
4464 | 0 | SetRecoveryPause(false); |
4465 | |
|
4466 | 0 | LocalPromoteIsTriggered = true; |
4467 | 0 | } |
4468 | | |
4469 | | /* |
4470 | | * Check whether a promote request has arrived. |
4471 | | */ |
4472 | | static bool |
4473 | | CheckForStandbyTrigger(void) |
4474 | | { |
4475 | | if (LocalPromoteIsTriggered) |
4476 | | return true; |
4477 | | |
4478 | | if (IsPromoteSignaled() && CheckPromoteSignal()) |
4479 | | { |
4480 | | ereport(LOG, (errmsg("received promote request"))); |
4481 | | RemovePromoteSignalFiles(); |
4482 | | ResetPromoteSignaled(); |
4483 | | SetPromoteIsTriggered(); |
4484 | | return true; |
4485 | | } |
4486 | | |
4487 | | return false; |
4488 | | } |
4489 | | |
4490 | | /* |
4491 | | * Remove the files signaling a standby promotion request. |
4492 | | */ |
4493 | | void |
4494 | | RemovePromoteSignalFiles(void) |
4495 | 0 | { |
4496 | 0 | unlink(PROMOTE_SIGNAL_FILE); |
4497 | 0 | } |
4498 | | |
4499 | | /* |
4500 | | * Check to see if a promote request has arrived. |
4501 | | */ |
4502 | | bool |
4503 | | CheckPromoteSignal(void) |
4504 | 0 | { |
4505 | 0 | struct stat stat_buf; |
4506 | |
|
4507 | 0 | if (stat(PROMOTE_SIGNAL_FILE, &stat_buf) == 0) |
4508 | 0 | return true; |
4509 | | |
4510 | 0 | return false; |
4511 | 0 | } |
4512 | | |
4513 | | /* |
4514 | | * Has hot standby initialization started pg_subtrans? |
4515 | | */ |
4516 | | bool |
4517 | | RecoverySubtransInitialized(void) |
4518 | 0 | { |
4519 | 0 | bool result; |
4520 | |
|
4521 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4522 | 0 | result = XLogRecoveryCtl->SharedRecoverySubtransInitialized; |
4523 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4524 | |
|
4525 | 0 | return result; |
4526 | 0 | } |
4527 | | |
4528 | | /* |
4529 | | * Remember that hot standby initialization has started pg_subtrans. |
4530 | | */ |
4531 | | void |
4532 | | SetRecoverySubtransInitialized(void) |
4533 | 0 | { |
4534 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4535 | 0 | XLogRecoveryCtl->SharedRecoverySubtransInitialized = true; |
4536 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4537 | 0 | } |
4538 | | |
4539 | | /* |
4540 | | * Wake up startup process to replay newly arrived WAL, or to notice that |
4541 | | * failover has been requested. |
4542 | | */ |
4543 | | void |
4544 | | WakeupRecovery(void) |
4545 | 0 | { |
4546 | 0 | SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch); |
4547 | 0 | } |
4548 | | |
4549 | | /* |
4550 | | * Schedule a walreceiver wakeup in the main recovery loop. |
4551 | | */ |
4552 | | void |
4553 | | XLogRequestWalReceiverReply(void) |
4554 | 0 | { |
4555 | 0 | doRequestWalReceiverReply = true; |
4556 | 0 | } |
4557 | | |
4558 | | /* |
4559 | | * Is HotStandby active yet? This is only important in special backends |
4560 | | * since normal backends won't ever be able to connect until this returns |
4561 | | * true. Postmaster knows this by way of signal, not via shared memory. |
4562 | | * |
4563 | | * Unlike testing standbyState, this works in any process that's connected to |
4564 | | * shared memory. (And note that standbyState alone doesn't tell the truth |
4565 | | * anyway.) |
4566 | | */ |
4567 | | bool |
4568 | | HotStandbyActive(void) |
4569 | 0 | { |
4570 | | /* |
4571 | | * We check shared state each time only until Hot Standby is active. We |
4572 | | * can't de-activate Hot Standby, so there's no need to keep checking |
4573 | | * after the shared variable has once been seen true. |
4574 | | */ |
4575 | 0 | if (LocalHotStandbyActive) |
4576 | 0 | return true; |
4577 | 0 | else |
4578 | 0 | { |
4579 | | /* spinlock is essential on machines with weak memory ordering! */ |
4580 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4581 | 0 | LocalHotStandbyActive = XLogRecoveryCtl->SharedHotStandbyActive; |
4582 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4583 | |
|
4584 | 0 | return LocalHotStandbyActive; |
4585 | 0 | } |
4586 | 0 | } |
4587 | | |
4588 | | /* |
4589 | | * Like HotStandbyActive(), but to be used only in WAL replay code, |
4590 | | * where we don't need to ask any other process what the state is. |
4591 | | */ |
4592 | | static bool |
4593 | | HotStandbyActiveInReplay(void) |
4594 | 0 | { |
4595 | 0 | Assert(AmStartupProcess() || !IsPostmasterEnvironment); |
4596 | 0 | return LocalHotStandbyActive; |
4597 | 0 | } |
4598 | | |
4599 | | /* |
4600 | | * Get latest redo apply position. |
4601 | | * |
4602 | | * Exported to allow WALReceiver to read the pointer directly. |
4603 | | */ |
4604 | | XLogRecPtr |
4605 | | GetXLogReplayRecPtr(TimeLineID *replayTLI) |
4606 | 0 | { |
4607 | 0 | XLogRecPtr recptr; |
4608 | 0 | TimeLineID tli; |
4609 | |
|
4610 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4611 | 0 | recptr = XLogRecoveryCtl->lastReplayedEndRecPtr; |
4612 | 0 | tli = XLogRecoveryCtl->lastReplayedTLI; |
4613 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4614 | |
|
4615 | 0 | if (replayTLI) |
4616 | 0 | *replayTLI = tli; |
4617 | 0 | return recptr; |
4618 | 0 | } |
4619 | | |
4620 | | |
4621 | | /* |
4622 | | * Get position of last applied, or the record being applied. |
4623 | | * |
4624 | | * This is different from GetXLogReplayRecPtr() in that if a WAL |
4625 | | * record is currently being applied, this includes that record. |
4626 | | */ |
4627 | | XLogRecPtr |
4628 | | GetCurrentReplayRecPtr(TimeLineID *replayEndTLI) |
4629 | 0 | { |
4630 | 0 | XLogRecPtr recptr; |
4631 | 0 | TimeLineID tli; |
4632 | |
|
4633 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4634 | 0 | recptr = XLogRecoveryCtl->replayEndRecPtr; |
4635 | 0 | tli = XLogRecoveryCtl->replayEndTLI; |
4636 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4637 | |
|
4638 | 0 | if (replayEndTLI) |
4639 | 0 | *replayEndTLI = tli; |
4640 | 0 | return recptr; |
4641 | 0 | } |
4642 | | |
4643 | | /* |
4644 | | * Save timestamp of latest processed commit/abort record. |
4645 | | * |
4646 | | * We keep this in XLogRecoveryCtl, not a simple static variable, so that it can be |
4647 | | * seen by processes other than the startup process. Note in particular |
4648 | | * that CreateRestartPoint is executed in the checkpointer. |
4649 | | */ |
4650 | | static void |
4651 | | SetLatestXTime(TimestampTz xtime) |
4652 | 0 | { |
4653 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4654 | 0 | XLogRecoveryCtl->recoveryLastXTime = xtime; |
4655 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4656 | 0 | } |
4657 | | |
4658 | | /* |
4659 | | * Fetch timestamp of latest processed commit/abort record. |
4660 | | */ |
4661 | | TimestampTz |
4662 | | GetLatestXTime(void) |
4663 | 0 | { |
4664 | 0 | TimestampTz xtime; |
4665 | |
|
4666 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4667 | 0 | xtime = XLogRecoveryCtl->recoveryLastXTime; |
4668 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4669 | |
|
4670 | 0 | return xtime; |
4671 | 0 | } |
4672 | | |
4673 | | /* |
4674 | | * Save timestamp of the next chunk of WAL records to apply. |
4675 | | * |
4676 | | * We keep this in XLogRecoveryCtl, not a simple static variable, so that it can be |
4677 | | * seen by all backends. |
4678 | | */ |
4679 | | static void |
4680 | | SetCurrentChunkStartTime(TimestampTz xtime) |
4681 | 0 | { |
4682 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4683 | 0 | XLogRecoveryCtl->currentChunkStartTime = xtime; |
4684 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4685 | 0 | } |
4686 | | |
4687 | | /* |
4688 | | * Fetch timestamp of latest processed commit/abort record. |
4689 | | * Startup process maintains an accurate local copy in XLogReceiptTime |
4690 | | */ |
4691 | | TimestampTz |
4692 | | GetCurrentChunkReplayStartTime(void) |
4693 | 0 | { |
4694 | 0 | TimestampTz xtime; |
4695 | |
|
4696 | 0 | SpinLockAcquire(&XLogRecoveryCtl->info_lck); |
4697 | 0 | xtime = XLogRecoveryCtl->currentChunkStartTime; |
4698 | 0 | SpinLockRelease(&XLogRecoveryCtl->info_lck); |
4699 | |
|
4700 | 0 | return xtime; |
4701 | 0 | } |
4702 | | |
4703 | | /* |
4704 | | * Returns time of receipt of current chunk of XLOG data, as well as |
4705 | | * whether it was received from streaming replication or from archives. |
4706 | | */ |
4707 | | void |
4708 | | GetXLogReceiptTime(TimestampTz *rtime, bool *fromStream) |
4709 | 0 | { |
4710 | | /* |
4711 | | * This must be executed in the startup process, since we don't export the |
4712 | | * relevant state to shared memory. |
4713 | | */ |
4714 | 0 | Assert(InRecovery); |
4715 | |
|
4716 | 0 | *rtime = XLogReceiptTime; |
4717 | 0 | *fromStream = (XLogReceiptSource == XLOG_FROM_STREAM); |
4718 | 0 | } |
4719 | | |
4720 | | /* |
4721 | | * Note that text field supplied is a parameter name and does not require |
4722 | | * translation |
4723 | | */ |
4724 | | void |
4725 | | RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue) |
4726 | 0 | { |
4727 | 0 | if (currValue < minValue) |
4728 | 0 | { |
4729 | 0 | if (HotStandbyActiveInReplay()) |
4730 | 0 | { |
4731 | 0 | bool warned_for_promote = false; |
4732 | |
|
4733 | 0 | ereport(WARNING, |
4734 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
4735 | 0 | errmsg("hot standby is not possible because of insufficient parameter settings"), |
4736 | 0 | errdetail("%s = %d is a lower setting than on the primary server, where its value was %d.", |
4737 | 0 | param_name, |
4738 | 0 | currValue, |
4739 | 0 | minValue))); |
4740 | | |
4741 | 0 | SetRecoveryPause(true); |
4742 | |
|
4743 | 0 | ereport(LOG, |
4744 | 0 | (errmsg("recovery has paused"), |
4745 | 0 | errdetail("If recovery is unpaused, the server will shut down."), |
4746 | 0 | errhint("You can then restart the server after making the necessary configuration changes."))); |
4747 | | |
4748 | 0 | while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED) |
4749 | 0 | { |
4750 | 0 | ProcessStartupProcInterrupts(); |
4751 | |
|
4752 | 0 | if (CheckForStandbyTrigger()) |
4753 | 0 | { |
4754 | 0 | if (!warned_for_promote) |
4755 | 0 | ereport(WARNING, |
4756 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
4757 | 0 | errmsg("promotion is not possible because of insufficient parameter settings"), |
4758 | | |
4759 | | /* |
4760 | | * Repeat the detail from above so it's easy to find |
4761 | | * in the log. |
4762 | | */ |
4763 | 0 | errdetail("%s = %d is a lower setting than on the primary server, where its value was %d.", |
4764 | 0 | param_name, |
4765 | 0 | currValue, |
4766 | 0 | minValue), |
4767 | 0 | errhint("Restart the server after making the necessary configuration changes."))); |
4768 | 0 | warned_for_promote = true; |
4769 | 0 | } |
4770 | | |
4771 | | /* |
4772 | | * If recovery pause is requested then set it paused. While |
4773 | | * we are in the loop, user might resume and pause again so |
4774 | | * set this every time. |
4775 | | */ |
4776 | 0 | ConfirmRecoveryPaused(); |
4777 | | |
4778 | | /* |
4779 | | * We wait on a condition variable that will wake us as soon |
4780 | | * as the pause ends, but we use a timeout so we can check the |
4781 | | * above conditions periodically too. |
4782 | | */ |
4783 | 0 | ConditionVariableTimedSleep(&XLogRecoveryCtl->recoveryNotPausedCV, 1000, |
4784 | 0 | WAIT_EVENT_RECOVERY_PAUSE); |
4785 | 0 | } |
4786 | 0 | ConditionVariableCancelSleep(); |
4787 | 0 | } |
4788 | | |
4789 | 0 | ereport(FATAL, |
4790 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
4791 | 0 | errmsg("recovery aborted because of insufficient parameter settings"), |
4792 | | /* Repeat the detail from above so it's easy to find in the log. */ |
4793 | 0 | errdetail("%s = %d is a lower setting than on the primary server, where its value was %d.", |
4794 | 0 | param_name, |
4795 | 0 | currValue, |
4796 | 0 | minValue), |
4797 | 0 | errhint("You can restart the server after making the necessary configuration changes."))); |
4798 | 0 | } |
4799 | 0 | } |
4800 | | |
4801 | | |
4802 | | /* |
4803 | | * GUC check_hook for primary_slot_name |
4804 | | */ |
4805 | | bool |
4806 | | check_primary_slot_name(char **newval, void **extra, GucSource source) |
4807 | 2 | { |
4808 | 2 | int err_code; |
4809 | 2 | char *err_msg = NULL; |
4810 | 2 | char *err_hint = NULL; |
4811 | | |
4812 | 2 | if (*newval && strcmp(*newval, "") != 0 && |
4813 | 0 | !ReplicationSlotValidateNameInternal(*newval, false, &err_code, |
4814 | 0 | &err_msg, &err_hint)) |
4815 | 0 | { |
4816 | 0 | GUC_check_errcode(err_code); |
4817 | 0 | GUC_check_errdetail("%s", err_msg); |
4818 | 0 | if (err_hint != NULL) |
4819 | 0 | GUC_check_errhint("%s", err_hint); |
4820 | 0 | return false; |
4821 | 0 | } |
4822 | | |
4823 | 2 | return true; |
4824 | 2 | } |
4825 | | |
4826 | | /* |
4827 | | * Return the recovery target derived from the recovery_target* settings, |
4828 | | * raising an error if more than one of them is set. |
4829 | | */ |
4830 | | static RecoveryTargetType |
4831 | | DetermineRecoveryTargetType(void) |
4832 | 0 | { |
4833 | 0 | int ntargets = 0; |
4834 | 0 | RecoveryTargetType target = RECOVERY_TARGET_UNSET; |
4835 | 0 | const char *val; |
4836 | 0 | StringInfoData buf; |
4837 | |
|
4838 | 0 | initStringInfo(&buf); |
4839 | |
|
4840 | 0 | #define ADD_TARGET_IF_SET(gucname, kind) \ |
4841 | 0 | do { \ |
4842 | 0 | val = GetConfigOption(gucname, false, false); \ |
4843 | 0 | if (val[0] != '\0') \ |
4844 | 0 | { \ |
4845 | 0 | ntargets++; \ |
4846 | 0 | target = (kind); \ |
4847 | 0 | if (buf.len == 0) \ |
4848 | 0 | appendStringInfo(&buf, _("\"%s\""), gucname); \ |
4849 | 0 | else \ |
4850 | 0 | appendStringInfo(&buf, _(", \"%s\""), gucname); \ |
4851 | 0 | } \ |
4852 | 0 | } while (0) |
4853 | |
|
4854 | 0 | ADD_TARGET_IF_SET("recovery_target", RECOVERY_TARGET_IMMEDIATE); |
4855 | 0 | ADD_TARGET_IF_SET("recovery_target_lsn", RECOVERY_TARGET_LSN); |
4856 | 0 | ADD_TARGET_IF_SET("recovery_target_name", RECOVERY_TARGET_NAME); |
4857 | 0 | ADD_TARGET_IF_SET("recovery_target_time", RECOVERY_TARGET_TIME); |
4858 | 0 | ADD_TARGET_IF_SET("recovery_target_xid", RECOVERY_TARGET_XID); |
4859 | 0 | #undef ADD_TARGET_IF_SET |
4860 | |
|
4861 | 0 | if (ntargets > 1) |
4862 | 0 | ereport(FATAL, |
4863 | 0 | errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
4864 | 0 | errmsg("cannot specify more than one recovery target"), |
4865 | 0 | errdetail("Parameters set are: %s.", |
4866 | 0 | buf.data)); |
4867 | | |
4868 | 0 | pfree(buf.data); |
4869 | |
|
4870 | 0 | return target; |
4871 | 0 | } |
4872 | | |
4873 | | /* |
4874 | | * GUC check_hook for recovery_target |
4875 | | */ |
4876 | | bool |
4877 | | check_recovery_target(char **newval, void **extra, GucSource source) |
4878 | 2 | { |
4879 | 2 | if (strcmp(*newval, "immediate") != 0 && strcmp(*newval, "") != 0) |
4880 | 0 | { |
4881 | 0 | GUC_check_errdetail("The only allowed value is \"immediate\"."); |
4882 | 0 | return false; |
4883 | 0 | } |
4884 | 2 | return true; |
4885 | 2 | } |
4886 | | |
4887 | | /* |
4888 | | * GUC check_hook for recovery_target_lsn |
4889 | | */ |
4890 | | bool |
4891 | | check_recovery_target_lsn(char **newval, void **extra, GucSource source) |
4892 | 2 | { |
4893 | 2 | if (strcmp(*newval, "") != 0) |
4894 | 0 | { |
4895 | 0 | XLogRecPtr lsn; |
4896 | 0 | XLogRecPtr *myextra; |
4897 | 0 | ErrorSaveContext escontext = {T_ErrorSaveContext}; |
4898 | |
|
4899 | 0 | lsn = pg_lsn_in_safe(*newval, (Node *) &escontext); |
4900 | 0 | if (escontext.error_occurred) |
4901 | 0 | return false; |
4902 | | |
4903 | 0 | myextra = (XLogRecPtr *) guc_malloc(LOG, sizeof(XLogRecPtr)); |
4904 | 0 | if (!myextra) |
4905 | 0 | return false; |
4906 | 0 | *myextra = lsn; |
4907 | 0 | *extra = myextra; |
4908 | 0 | } |
4909 | 2 | return true; |
4910 | 2 | } |
4911 | | |
4912 | | /* |
4913 | | * GUC assign_hook for recovery_target_lsn |
4914 | | */ |
4915 | | void |
4916 | | assign_recovery_target_lsn(const char *newval, void *extra) |
4917 | 2 | { |
4918 | 2 | if (newval && strcmp(newval, "") != 0) |
4919 | 0 | recoveryTargetLSN = *((XLogRecPtr *) extra); |
4920 | 2 | } |
4921 | | |
4922 | | /* |
4923 | | * GUC check_hook for recovery_target_name |
4924 | | */ |
4925 | | bool |
4926 | | check_recovery_target_name(char **newval, void **extra, GucSource source) |
4927 | 2 | { |
4928 | | /* Use the value of newval directly */ |
4929 | 2 | if (strlen(*newval) >= MAXFNAMELEN) |
4930 | 0 | { |
4931 | 0 | GUC_check_errdetail("\"%s\" is too long (maximum %d characters).", |
4932 | 0 | "recovery_target_name", MAXFNAMELEN - 1); |
4933 | 0 | return false; |
4934 | 0 | } |
4935 | 2 | return true; |
4936 | 2 | } |
4937 | | |
4938 | | /* |
4939 | | * GUC check_hook for recovery_target_time |
4940 | | * |
4941 | | * The interpretation of the recovery_target_time string can depend on the |
4942 | | * time zone setting, so we need to wait until after all GUC processing is |
4943 | | * done before we can do the final parsing of the string. This check function |
4944 | | * only does a parsing pass to catch syntax errors, but we store the string |
4945 | | * and parse it again when we need to use it. |
4946 | | */ |
4947 | | bool |
4948 | | check_recovery_target_time(char **newval, void **extra, GucSource source) |
4949 | 2 | { |
4950 | 2 | if (strcmp(*newval, "") != 0) |
4951 | 0 | { |
4952 | | /* reject some special values */ |
4953 | 0 | if (strcmp(*newval, "now") == 0 || |
4954 | 0 | strcmp(*newval, "today") == 0 || |
4955 | 0 | strcmp(*newval, "tomorrow") == 0 || |
4956 | 0 | strcmp(*newval, "yesterday") == 0) |
4957 | 0 | { |
4958 | 0 | return false; |
4959 | 0 | } |
4960 | | |
4961 | | /* |
4962 | | * parse timestamp value (see also timestamptz_in()) |
4963 | | */ |
4964 | 0 | { |
4965 | 0 | char *str = *newval; |
4966 | 0 | fsec_t fsec; |
4967 | 0 | struct pg_tm tt, |
4968 | 0 | *tm = &tt; |
4969 | 0 | int tz; |
4970 | 0 | int dtype; |
4971 | 0 | int nf; |
4972 | 0 | int dterr; |
4973 | 0 | char *field[MAXDATEFIELDS]; |
4974 | 0 | int ftype[MAXDATEFIELDS]; |
4975 | 0 | char workbuf[MAXDATELEN + MAXDATEFIELDS]; |
4976 | 0 | DateTimeErrorExtra dtextra; |
4977 | 0 | TimestampTz timestamp; |
4978 | |
|
4979 | 0 | dterr = ParseDateTime(str, workbuf, sizeof(workbuf), |
4980 | 0 | field, ftype, MAXDATEFIELDS, &nf); |
4981 | 0 | if (dterr == 0) |
4982 | 0 | dterr = DecodeDateTime(field, ftype, nf, |
4983 | 0 | &dtype, tm, &fsec, &tz, &dtextra); |
4984 | 0 | if (dterr != 0) |
4985 | 0 | return false; |
4986 | 0 | if (dtype != DTK_DATE) |
4987 | 0 | return false; |
4988 | | |
4989 | 0 | if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0) |
4990 | 0 | { |
4991 | 0 | GUC_check_errdetail("Timestamp out of range: \"%s\".", str); |
4992 | 0 | return false; |
4993 | 0 | } |
4994 | 0 | } |
4995 | 0 | } |
4996 | 2 | return true; |
4997 | 2 | } |
4998 | | |
4999 | | /* |
5000 | | * GUC check_hook for recovery_target_timeline |
5001 | | */ |
5002 | | bool |
5003 | | check_recovery_target_timeline(char **newval, void **extra, GucSource source) |
5004 | 2 | { |
5005 | 2 | RecoveryTargetTimeLineGoal rttg; |
5006 | 2 | RecoveryTargetTimeLineGoal *myextra; |
5007 | | |
5008 | 2 | if (strcmp(*newval, "current") == 0) |
5009 | 0 | rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE; |
5010 | 2 | else if (strcmp(*newval, "latest") == 0) |
5011 | 2 | rttg = RECOVERY_TARGET_TIMELINE_LATEST; |
5012 | 0 | else |
5013 | 0 | { |
5014 | 0 | char *endp; |
5015 | 0 | uint64 timeline; |
5016 | |
|
5017 | 0 | rttg = RECOVERY_TARGET_TIMELINE_NUMERIC; |
5018 | |
|
5019 | 0 | errno = 0; |
5020 | 0 | timeline = strtou64(*newval, &endp, 0); |
5021 | |
|
5022 | 0 | if (*endp != '\0' || errno == EINVAL || errno == ERANGE) |
5023 | 0 | { |
5024 | 0 | GUC_check_errdetail("\"%s\" is not a valid number.", |
5025 | 0 | "recovery_target_timeline"); |
5026 | 0 | return false; |
5027 | 0 | } |
5028 | | |
5029 | 0 | if (timeline < 1 || timeline > PG_UINT32_MAX) |
5030 | 0 | { |
5031 | 0 | GUC_check_errdetail("\"%s\" must be between %u and %u.", |
5032 | 0 | "recovery_target_timeline", 1, PG_UINT32_MAX); |
5033 | 0 | return false; |
5034 | 0 | } |
5035 | 0 | } |
5036 | | |
5037 | 2 | myextra = (RecoveryTargetTimeLineGoal *) guc_malloc(LOG, sizeof(RecoveryTargetTimeLineGoal)); |
5038 | 2 | if (!myextra) |
5039 | 0 | return false; |
5040 | 2 | *myextra = rttg; |
5041 | 2 | *extra = myextra; |
5042 | | |
5043 | 2 | return true; |
5044 | 2 | } |
5045 | | |
5046 | | /* |
5047 | | * GUC assign_hook for recovery_target_timeline |
5048 | | */ |
5049 | | void |
5050 | | assign_recovery_target_timeline(const char *newval, void *extra) |
5051 | 2 | { |
5052 | 2 | recoveryTargetTimeLineGoal = *((RecoveryTargetTimeLineGoal *) extra); |
5053 | 2 | if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC) |
5054 | 0 | recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0); |
5055 | 2 | else |
5056 | 2 | recoveryTargetTLIRequested = 0; |
5057 | 2 | } |
5058 | | |
5059 | | /* |
5060 | | * GUC check_hook for recovery_target_xid |
5061 | | */ |
5062 | | bool |
5063 | | check_recovery_target_xid(char **newval, void **extra, GucSource source) |
5064 | 2 | { |
5065 | 2 | if (strcmp(*newval, "") != 0) |
5066 | 0 | { |
5067 | 0 | TransactionId xid; |
5068 | 0 | TransactionId *myextra; |
5069 | 0 | char *endp; |
5070 | 0 | char *val; |
5071 | |
|
5072 | 0 | errno = 0; |
5073 | | |
5074 | | /* |
5075 | | * Consume leading whitespace to determine if number is negative |
5076 | | */ |
5077 | 0 | val = *newval; |
5078 | |
|
5079 | 0 | while (isspace((unsigned char) *val)) |
5080 | 0 | val++; |
5081 | | |
5082 | | /* |
5083 | | * This cast will remove the epoch, if any |
5084 | | */ |
5085 | 0 | xid = (TransactionId) strtou64(val, &endp, 0); |
5086 | |
|
5087 | 0 | if (*endp != '\0' || errno == EINVAL || errno == ERANGE || *val == '-') |
5088 | 0 | { |
5089 | 0 | GUC_check_errdetail("\"%s\" is not a valid number.", |
5090 | 0 | "recovery_target_xid"); |
5091 | 0 | return false; |
5092 | 0 | } |
5093 | | |
5094 | 0 | if (xid < FirstNormalTransactionId) |
5095 | 0 | { |
5096 | 0 | GUC_check_errdetail("\"%s\" without epoch must be greater than or equal to %u.", |
5097 | 0 | "recovery_target_xid", |
5098 | 0 | FirstNormalTransactionId); |
5099 | 0 | return false; |
5100 | 0 | } |
5101 | | |
5102 | 0 | myextra = (TransactionId *) guc_malloc(LOG, sizeof(TransactionId)); |
5103 | 0 | if (!myextra) |
5104 | 0 | return false; |
5105 | 0 | *myextra = xid; |
5106 | 0 | *extra = myextra; |
5107 | 0 | } |
5108 | 2 | return true; |
5109 | 2 | } |
5110 | | |
5111 | | /* |
5112 | | * GUC assign_hook for recovery_target_xid |
5113 | | */ |
5114 | | void |
5115 | | assign_recovery_target_xid(const char *newval, void *extra) |
5116 | 2 | { |
5117 | 2 | if (newval && strcmp(newval, "") != 0) |
5118 | 0 | recoveryTargetXid = *((TransactionId *) extra); |
5119 | 2 | } |