Author: Noah Misch Commit: Noah Misch Skip WAL recycling and preallocation during archive recovery. The previous commit addressed the chief consequences of a race condition between InstallXLogFileSegment() and KeepFileRestoredFromArchive(). Fix three lesser consequences. A spurious durable_rename_excl() LOG message remained possible. KeepFileRestoredFromArchive() wasted the proceeds of WAL recycling and preallocation. Finally, XLogFileInitInternal() could return a descriptor for a file that KeepFileRestoredFromArchive() had already unlinked. That felt like a recipe for future bugs. Discussion: https://postgr.es/m/20210202151416.GB3304930@rfd.leadboat.com diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 8cda20d..2c6e21b 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -663,6 +663,16 @@ typedef struct XLogCtlData bool SharedHotStandbyActive; /* + * InstallXLogFileSegmentActive indicates whether the checkpointer should + * arrange for future segments by recycling and/or PreallocXlogFiles(). + * Protected by ControlFileLock. Only the startup process changes it. If + * true, anyone can use InstallXLogFileSegment(). If false, the startup + * process owns the exclusive right to install segments, by reading from + * the archive and possibly replacing existing files. + */ + bool InstallXLogFileSegmentActive; + + /* * SharedPromoteIsTriggered indicates if a standby promotion has been * triggered. Protected by info_lck. */ @@ -921,6 +931,7 @@ static int XLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr, int reqLen, XLogRecPtr targetRecPtr, char *readBuf); static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, bool fetching_ckpt, XLogRecPtr tliRecPtr); +static void XLogShutdownWalRcv(void); static int emode_for_corrupt_record(int emode, XLogRecPtr RecPtr); static void XLogFileClose(void); static void PreallocXlogFiles(XLogRecPtr endptr); @@ -3625,8 +3636,8 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno, * is false.) * * Returns true if the file was installed successfully. false indicates that - * max_segno limit was exceeded, or an error occurred while renaming the - * file into place. + * max_segno limit was exceeded, the startup process has disabled this + * function for now, or an error occurred while renaming the file into place. */ static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath, @@ -3638,6 +3649,11 @@ InstallXLogFileSegment(XLogSegNo *segno, char *tmppath, XLogFilePath(path, ThisTimeLineID, *segno, wal_segment_size); LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + if (!XLogCtl->InstallXLogFileSegmentActive) + { + LWLockRelease(ControlFileLock); + return false; + } if (!find_free) { @@ -3745,6 +3761,7 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli, */ if (source == XLOG_FROM_ARCHIVE) { + Assert(!XLogCtl->InstallXLogFileSegmentActive); KeepFileRestoredFromArchive(path, xlogfname); /* @@ -3946,6 +3963,9 @@ PreallocXlogFiles(XLogRecPtr endptr) char path[MAXPGPATH]; uint64 offset; + if (!XLogCtl->InstallXLogFileSegmentActive) + return; /* unlocked check says no */ + XLByteToPrevSeg(endptr, _logSegNo, wal_segment_size); offset = XLogSegmentOffset(endptr - 1, wal_segment_size); if (offset >= (uint32) (0.75 * wal_segment_size)) @@ -4227,6 +4247,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo, */ if (wal_recycle && *endlogSegNo <= recycleSegNo && + XLogCtl->InstallXLogFileSegmentActive && /* callee rechecks this */ lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) && InstallXLogFileSegment(endlogSegNo, path, true, recycleSegNo)) @@ -4240,7 +4261,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo, } else { - /* No need for any more future segments... */ + /* No need for any more future segments, or recycling failed ... */ int rc; ereport(DEBUG2, @@ -5226,6 +5247,7 @@ XLOGShmemInit(void) XLogCtl->XLogCacheBlck = XLOGbuffers - 1; XLogCtl->SharedRecoveryState = RECOVERY_STATE_CRASH; XLogCtl->SharedHotStandbyActive = false; + XLogCtl->InstallXLogFileSegmentActive = false; XLogCtl->SharedPromoteIsTriggered = false; XLogCtl->WalWriterSleeping = false; @@ -5253,6 +5275,11 @@ BootStrapXLOG(void) struct timeval tv; pg_crc32c crc; + /* allow ordinary WAL segment creation, like StartupXLOG() would */ + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + XLogCtl->InstallXLogFileSegmentActive = true; + LWLockRelease(ControlFileLock); + /* * Select a hopefully-unique system identifier code for this installation. * We use the result of gettimeofday(), including the fractional seconds @@ -7619,7 +7646,7 @@ StartupXLOG(void) * the startup checkpoint record. It will trump over the checkpoint and * subsequent records if it's still alive when we start writing WAL. */ - ShutdownWalRcv(); + XLogShutdownWalRcv(); /* * Reset unlogged relations to the contents of their INIT fork. This is @@ -7644,7 +7671,7 @@ StartupXLOG(void) * recovery, e.g., timeline history file) from archive or pg_wal. * * Note that standby mode must be turned off after killing WAL receiver, - * i.e., calling ShutdownWalRcv(). + * i.e., calling XLogShutdownWalRcv(). */ Assert(!WalRcvStreaming()); StandbyMode = false; @@ -7710,6 +7737,14 @@ StartupXLOG(void) oldestActiveXID = PrescanPreparedTransactions(NULL, NULL); /* + * Allow ordinary WAL segment creation before any exitArchiveRecovery(), + * which sometimes creates a segment, and after the last ReadRecord(). + */ + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + XLogCtl->InstallXLogFileSegmentActive = true; + LWLockRelease(ControlFileLock); + + /* * Consider whether we need to assign a new timeline ID. * * If we are doing an archive recovery, we always assign a new ID. This @@ -12378,7 +12413,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, */ if (StandbyMode && CheckForStandbyTrigger()) { - ShutdownWalRcv(); + XLogShutdownWalRcv(); return false; } @@ -12426,7 +12461,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * WAL that we restore from archive. */ if (WalRcvStreaming()) - ShutdownWalRcv(); + XLogShutdownWalRcv(); /* * Before we sleep, re-scan for possible new timelines if @@ -12553,7 +12588,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, */ if (pendingWalRcvRestart && !startWalReceiver) { - ShutdownWalRcv(); + XLogShutdownWalRcv(); /* * Re-scan for possible new timelines if we were @@ -12603,6 +12638,9 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, tli, curFileTLI); } curFileTLI = tli; + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + XLogCtl->InstallXLogFileSegmentActive = true; + LWLockRelease(ControlFileLock); RequestXLogStreaming(tli, ptr, PrimaryConnInfo, PrimarySlotName, wal_receiver_create_temp_slot); @@ -12770,6 +12808,17 @@ StartupRequestWalReceiverRestart(void) } } +/* Thin wrapper around ShutdownWalRcv(). */ +static void +XLogShutdownWalRcv(void) +{ + ShutdownWalRcv(); + + LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); + XLogCtl->InstallXLogFileSegmentActive = false; + LWLockRelease(ControlFileLock); +} + /* * Determine what log level should be used to report a corrupt WAL record * in the current WAL page, previously read by XLogPageRead().