diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 2618c8d3d383b00b2fe3eb1e436301c3ab5744c4..9bd7f03b8673f61fe83919736f25b542f462c053 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -605,6 +605,7 @@ static void SetLatestXTime(TimestampTz xtime); static void SetCurrentChunkStartTime(TimestampTz xtime); static void CheckRequiredParameterValues(void); static void XLogReportParameters(void); +static void checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI); static void LocalSetXLogInsertAllowed(void); static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags); static void KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo); @@ -5909,12 +5910,41 @@ StartupXLOG(void) LWLockRelease(XidGenLock); } + /* + * Before replaying this record, check if it is a shutdown + * checkpoint record that causes the current timeline to + * change. The checkpoint record is already considered to be + * part of the new timeline, so we update ThisTimeLineID + * before replaying it. That's important so that replayEndTLI, + * which is recorded as the minimum recovery point's TLI if + * recovery stops after this record, is set correctly. + */ + if (record->xl_rmid == RM_XLOG_ID && + (record->xl_info & ~XLR_INFO_MASK) == XLOG_CHECKPOINT_SHUTDOWN) + { + CheckPoint checkPoint; + TimeLineID newTLI; + + memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint)); + newTLI = checkPoint.ThisTimeLineID; + + if (newTLI != ThisTimeLineID) + { + /* Check that it's OK to switch to this TLI */ + checkTimeLineSwitch(EndRecPtr, newTLI); + + /* Following WAL records should be run with new TLI */ + ThisTimeLineID = newTLI; + } + } + /* * Update shared replayEndRecPtr before replaying this record, * so that XLogFlush will update minRecoveryPoint correctly. */ SpinLockAcquire(&xlogctl->info_lck); xlogctl->replayEndRecPtr = EndRecPtr; + xlogctl->replayEndTLI = ThisTimeLineID; SpinLockRelease(&xlogctl->info_lck); /* @@ -7858,6 +7888,48 @@ UpdateFullPageWrites(void) END_CRIT_SECTION(); } +/* + * Check that it's OK to switch to new timeline during recovery. + * + * 'lsn' is the address of the shutdown checkpoint record we're about to + * replay. (Currently, timeline can only change at a shutdown checkpoint). + */ +static void +checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI) +{ + /* + * The new timeline better be in the list of timelines we expect + * to see, according to the timeline history. It should also not + * decrease. + */ + if (newTLI < ThisTimeLineID || !tliInHistory(newTLI, expectedTLEs)) + ereport(PANIC, + (errmsg("unexpected timeline ID %u (after %u) in checkpoint record", + newTLI, ThisTimeLineID))); + + /* + * If we have not yet reached min recovery point, and we're about + * to switch to a timeline greater than the timeline of the min + * recovery point: trouble. After switching to the new timeline, + * we could not possibly visit the min recovery point on the + * correct timeline anymore. This can happen if there is a newer + * timeline in the archive that branched before the timeline the + * min recovery point is on, and you attempt to do PITR to the + * new timeline. + */ + if (!XLogRecPtrIsInvalid(minRecoveryPoint) && + XLByteLT(lsn, minRecoveryPoint) && + newTLI > minRecoveryPointTLI) + ereport(PANIC, + (errmsg("unexpected timeline ID %u in checkpoint record, before reaching minimum recovery point %X/%X on timeline %u", + newTLI, + (uint32) (minRecoveryPoint >> 32), + (uint32) minRecoveryPoint, + minRecoveryPointTLI))); + + /* Looks good */ +} + /* * XLOG resource manager's routines * @@ -7971,44 +8043,13 @@ xlog_redo(XLogRecPtr lsn, XLogRecord *record) } /* - * TLI may change in a shutdown checkpoint. + * We should've already switched to the new TLI before replaying this + * record. */ if (checkPoint.ThisTimeLineID != ThisTimeLineID) - { - /* - * The new timeline better be in the list of timelines we expect - * to see, according to the timeline history. It should also not - * decrease. - */ - if (checkPoint.ThisTimeLineID < ThisTimeLineID || - !tliInHistory(checkPoint.ThisTimeLineID, expectedTLEs)) - ereport(PANIC, - (errmsg("unexpected timeline ID %u (after %u) in checkpoint record", - checkPoint.ThisTimeLineID, ThisTimeLineID))); - - /* - * If we have not yet reached min recovery point, and we're about - * to switch to a timeline greater than the timeline of the min - * recovery point: trouble. After switching to the new timeline, - * we could not possibly visit the min recovery point on the - * correct timeline anymore. This can happen if there is a newer - * timeline in the archive that branched before the timeline the - * min recovery point is on, and you attempt to do PITR to the - * new timeline. - */ - if (!XLogRecPtrIsInvalid(minRecoveryPoint) && - XLByteLT(lsn, minRecoveryPoint) && - checkPoint.ThisTimeLineID > minRecoveryPointTLI) - ereport(PANIC, - (errmsg("unexpected timeline ID %u in checkpoint record, before reaching minimum recovery point %X/%X on timeline %u", - checkPoint.ThisTimeLineID, - (uint32) (minRecoveryPoint >> 32), - (uint32) minRecoveryPoint, - minRecoveryPointTLI))); - - /* Following WAL records should be run with new TLI */ - ThisTimeLineID = checkPoint.ThisTimeLineID; - } + ereport(PANIC, + (errmsg("unexpected timeline ID %u (should be %u) in checkpoint record", + checkPoint.ThisTimeLineID, ThisTimeLineID))); RecoveryRestartPoint(&checkPoint); }