removing global variable ThisTimeLineID

Started by Robert Haasabout 4 years ago16 messages
#1Robert Haas
robertmhaas@gmail.com
11 attachment(s)

Hi,

The global variable ThisTimeLineID is rather confusing. During
recovery, in the startup process, when we're reading a WAL record, it
is the timeline of the WAL record we are trying to read or have just
read, except when we're trying to read the initial checkpoint record,
when it's zero. In other processes, it's typically 0 throughout
recovery, but sometimes it's not, because for example
CreateRestartPoint temporarily sets it to the timeline from which
we're replaying WAL, since there's no other way to get
RemoveOldXlogFiles() to recycle files onto the right timeline, or
PreallocXlogFiles() to preallocate onto the right timeline. Similarly,
walreceiver and walsender find it convenient to make ThisTimeLineID
the timeline from which WAL is being replayed or at least the one from
which it was being replayed at last check. logicalfuncs.c and
slotfuncs.c set the global variable sometimes too, apparently just for
fun, as the value doesn't seem to get used for anything. In normal
running, once the startup process exits, the next call to
RecoveryInProgress() sets the value to the timeline into which new WAL
can be inserted. Note also that all of this is different from
XLogCtl->ThisTimeLineID, which is set at the end of recovery and thus,
in the startup process, generally different from ThisTImeLineID, but
in other processes, generally the same as ThisTimeLineID.

At the risk of stating the obvious, using the same variable for
different purposes at different times is not a great programming
practice. Commits 2f5c4397c39dea49c5608ba583868e26d767fc32 and
902a2c280012557b85c7e0fce3f6f0e355cb2d69 show that the possibility of
programmer error is not zero, even though neither of those issues are
serious. Moreover, the global variable itself seems to do nothing but
invite programming errors. The name itself is a disaster. What is
"this" timeline ID? Well, uh, the right one, of course! We should be
more clear about what we mean: the timeline into which we are
inserting, the timeline from which we are replaying, the timeline from
which we are performing logical decoding. I suspect that having a
global variable here isn't even really saving us anything much, as a
value that does not change can be read from shared memory without a
lock.

I would like to clean this up. Attached is a series of patches which
try to do that. For ease of review, this is separated out into quite a
few separate patches, but most likely we'd combine some of them for
commit. Patches 0001 through 0004 eliminate all use of the global
variable ThisTimeLineID outside of xlog.c, and patch 0005 makes it
"static" so that it ceases to be visible outside of xlog.c. Patches
0006 to 0010 clean up uses of ThisTimeLineID itself, passing it around
as a function parameter, or otherwise arranging to fetch the relevant
timeline ID from someplace sensible rather than using the global
variable as a scratchpad. Finally, patch 0011 deletes the global
variable.

I have not made a serious attempt to clear up all of the
terminological confusion created by the term ThisTimeLineID, which
also appears as part of some structs, so you'll still see that name in
the source code after applying these patches, just not as the name of
a global variable. I have, however, used names like insertTLI and
replayTLI in many places changed by the patch, and I think that makes
it significantly more clear which timeline is being discussed in each
case. In some places I have endeavored to improve the comments. There
is doubtless more that could be done here, but I think this is a
fairly respectable start.

Opinions welcome.

Thanks,

--
Robert Haas
EDB: http://www.enterprisedb.com

Attachments:

0003-walsender.c-Don-t-rely-on-the-global-variable-ThisTi.patchapplication/octet-stream; name=0003-walsender.c-Don-t-rely-on-the-global-variable-ThisTi.patchDownload
From 9cd558add3c2d0fe68a77263c3ecaa38d0b09c27 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 29 Oct 2021 16:55:04 -0400
Subject: [PATCH 03/11] walsender.c: Don't rely on the global variable
 ThisTimeLineID.

IdentifySystem(), StartReplication(), and read_local_xlog_page() rely
on xlog.c to set ThisTimeLineID to the current timeline ID when not in
recovery; and when in recovery, they set ThisTimeLineID themsleves.
Instead, have them rely on only on local variables.  When not in
recovery, they now obtain the current timeline via GetFlushRecPtr(),
and when in recovery, they obtain it just as they did before.  As part
of this change, GetStandbyFlushRecPtr() now returns the TLI via an out
parameter rather than storing it into ThisTimeLineID.

logical_read_xlog_page() and ReadReplicationSlot() rely on
ThisTimeLineID to determine the current timeline, but only when
in normal running, since ReadReplicationSlot() uses an another
method when in recovery, and logical_read_xlog_page() can't run
during recovery. Use GetWALInsertionTimeLine() instead, and add
some comments highlighting the need for logical_read_xlog_page()
to be changed if we ever want to run logical decoding on standbys.

Because read_local_xlog_page() and logical_read_xlog_page() both
call XLogReadDetermineTimeline() which considers ThisTimeLineID
as a sort of implicit argument, update that function to take
the current system timeline as an explicit argument instead.

Remove the logic in XlogReadTwoPhaseData() which saves and restores
the value of ThisTimeLineID. Since the walsender.c functions are
no longer changing it, this isn't required any more.
---
 src/backend/access/transam/twophase.c  | 14 +-----
 src/backend/access/transam/xlogutils.c | 39 ++++++++-------
 src/backend/replication/walsender.c    | 67 +++++++++++++-------------
 src/include/access/xlogutils.h         |  4 +-
 4 files changed, 60 insertions(+), 64 deletions(-)

diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index f6e7fa71d8..ef4b5f639c 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -1373,11 +1373,7 @@ ReadTwoPhaseFile(TransactionId xid, bool missing_ok)
  * twophase files and ReadTwoPhaseFile should be used instead.
  *
  * Note clearly that this function can access WAL during normal operation,
- * similarly to the way WALSender or Logical Decoding would do.  While
- * accessing WAL, read_local_xlog_page() may change ThisTimeLineID,
- * particularly if this routine is called for the end-of-recovery checkpoint
- * in the checkpointer itself, so save the current timeline number value
- * and restore it once done.
+ * similarly to the way WALSender or Logical Decoding would do.
  */
 static void
 XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len)
@@ -1385,7 +1381,6 @@ XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len)
 	XLogRecord *record;
 	XLogReaderState *xlogreader;
 	char	   *errormsg;
-	TimeLineID	save_currtli = ThisTimeLineID;
 
 	xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
 									XL_ROUTINE(.page_read = &read_local_xlog_page,
@@ -1401,13 +1396,6 @@ XlogReadTwoPhaseData(XLogRecPtr lsn, char **buf, int *len)
 	XLogBeginRead(xlogreader, lsn);
 	record = XLogReadRecord(xlogreader, &errormsg);
 
-	/*
-	 * Restore immediately the timeline where it was previously, as
-	 * read_local_xlog_page() could have changed it if the record was read
-	 * while recovery was finishing or if the timeline has jumped in-between.
-	 */
-	ThisTimeLineID = save_currtli;
-
 	if (record == NULL)
 		ereport(ERROR,
 				(errcode_for_file_access(),
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index c40500a6f2..b33e0531ed 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -678,6 +678,10 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
  * wantLength to the amount of the page that will be read, up to
  * XLOG_BLCKSZ. If the amount to be read isn't known, pass XLOG_BLCKSZ.
  *
+ * The currTLI argument should be the system-wide current timeline.
+ * Note that this may be different from state->currTLI, which is the timeline
+ * from which the caller is currently reading previous xlog records.
+ *
  * We switch to an xlog segment from the new timeline eagerly when on a
  * historical timeline, as soon as we reach the start of the xlog segment
  * containing the timeline switch.  The server copied the segment to the new
@@ -699,12 +703,11 @@ XLogTruncateRelation(RelFileNode rnode, ForkNumber forkNum,
  *
  * The caller must also make sure it doesn't read past the current replay
  * position (using GetXLogReplayRecPtr) if executing in recovery, so it
- * doesn't fail to notice that the current timeline became historical. The
- * caller must also update ThisTimeLineID with the result of
- * GetXLogReplayRecPtr and must check RecoveryInProgress().
+ * doesn't fail to notice that the current timeline became historical.
  */
 void
-XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wantLength)
+XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage,
+						  uint32 wantLength, TimeLineID currTLI)
 {
 	const XLogRecPtr lastReadPage = (state->seg.ws_segno *
 									 state->segcxt.ws_segsize + state->segoff);
@@ -712,6 +715,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
 	Assert(wantPage != InvalidXLogRecPtr && wantPage % XLOG_BLCKSZ == 0);
 	Assert(wantLength <= XLOG_BLCKSZ);
 	Assert(state->readLen == 0 || state->readLen <= XLOG_BLCKSZ);
+	Assert(currTLI != 0);
 
 	/*
 	 * If the desired page is currently read in and valid, we have nothing to
@@ -732,12 +736,12 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
 	 * just carry on. (Seeking backwards requires a check to make sure the
 	 * older page isn't on a prior timeline).
 	 *
-	 * ThisTimeLineID might've become historical since we last looked, but the
-	 * caller is required not to read past the flush limit it saw at the time
-	 * it looked up the timeline. There's nothing we can do about it if
-	 * StartupXLOG() renames it to .partial concurrently.
+	 * currTLI might've become historical since the caller obtained the value,
+	 * but the caller is required not to read past the flush limit it saw at
+	 * the time it looked up the timeline. There's nothing we can do about it
+	 * if StartupXLOG() renames it to .partial concurrently.
 	 */
-	if (state->currTLI == ThisTimeLineID && wantPage >= lastReadPage)
+	if (state->currTLI == currTLI && wantPage >= lastReadPage)
 	{
 		Assert(state->currTLIValidUntil == InvalidXLogRecPtr);
 		return;
@@ -749,7 +753,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
 	 * the current segment we can just keep reading.
 	 */
 	if (state->currTLIValidUntil != InvalidXLogRecPtr &&
-		state->currTLI != ThisTimeLineID &&
+		state->currTLI != currTLI &&
 		state->currTLI != 0 &&
 		((wantPage + wantLength) / state->segcxt.ws_segsize) <
 		(state->currTLIValidUntil / state->segcxt.ws_segsize))
@@ -772,7 +776,7 @@ XLogReadDetermineTimeline(XLogReaderState *state, XLogRecPtr wantPage, uint32 wa
 		 * We need to re-read the timeline history in case it's been changed
 		 * by a promotion or replay from a cascaded replica.
 		 */
-		List	   *timelineHistory = readTimeLineHistory(ThisTimeLineID);
+		List	   *timelineHistory = readTimeLineHistory(currTLI);
 		XLogRecPtr	endOfSegment;
 
 		endOfSegment = ((wantPage / state->segcxt.ws_segsize) + 1) *
@@ -853,6 +857,7 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
 	TimeLineID	tli;
 	int			count;
 	WALReadError errinfo;
+	TimeLineID	currTLI;
 
 	loc = targetPagePtr + reqLen;
 
@@ -864,10 +869,10 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
 		 * most recent timeline is.
 		 */
 		if (!RecoveryInProgress())
-			read_upto = GetFlushRecPtr(&ThisTimeLineID);
+			read_upto = GetFlushRecPtr(&currTLI);
 		else
-			read_upto = GetXLogReplayRecPtr(&ThisTimeLineID);
-		tli = ThisTimeLineID;
+			read_upto = GetXLogReplayRecPtr(&currTLI);
+		tli = currTLI;
 
 		/*
 		 * Check which timeline to get the record from.
@@ -886,16 +891,16 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
 		 * archive in the timeline will get renamed to .partial by
 		 * StartupXLOG().
 		 *
-		 * If that happens after our caller updated ThisTimeLineID but before
+		 * If that happens after our caller determined the TLI but before
 		 * we actually read the xlog page, we might still try to read from the
 		 * old (now renamed) segment and fail. There's not much we can do
 		 * about this, but it can only happen when we're a leaf of a cascading
 		 * standby whose primary gets promoted while we're decoding, so a
 		 * one-off ERROR isn't too bad.
 		 */
-		XLogReadDetermineTimeline(state, targetPagePtr, reqLen);
+		XLogReadDetermineTimeline(state, targetPagePtr, reqLen, tli);
 
-		if (state->currTLI == ThisTimeLineID)
+		if (state->currTLI == currTLI)
 		{
 
 			if (loc <= read_upto)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d09bffaa9d..fff7dfc640 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -230,7 +230,7 @@ static void WalSndShutdown(void) pg_attribute_noreturn();
 static void XLogSendPhysical(void);
 static void XLogSendLogical(void);
 static void WalSndDone(WalSndSendDataCallback send_data);
-static XLogRecPtr GetStandbyFlushRecPtr(void);
+static XLogRecPtr GetStandbyFlushRecPtr(TimeLineID *tli);
 static void IdentifySystem(void);
 static void ReadReplicationSlot(ReadReplicationSlotCmd *cmd);
 static void CreateReplicationSlot(CreateReplicationSlotCmd *cmd);
@@ -385,6 +385,7 @@ IdentifySystem(void)
 	TupleDesc	tupdesc;
 	Datum		values[4];
 	bool		nulls[4];
+	TimeLineID	currTLI;
 
 	/*
 	 * Reply with a result set with one row, four columns. First col is system
@@ -397,12 +398,9 @@ IdentifySystem(void)
 
 	am_cascading_walsender = RecoveryInProgress();
 	if (am_cascading_walsender)
-	{
-		/* this also updates ThisTimeLineID */
-		logptr = GetStandbyFlushRecPtr();
-	}
+		logptr = GetStandbyFlushRecPtr(&currTLI);
 	else
-		logptr = GetFlushRecPtr(&ThisTimeLineID);
+		logptr = GetFlushRecPtr(&currTLI);
 
 	snprintf(xloc, sizeof(xloc), "%X/%X", LSN_FORMAT_ARGS(logptr));
 
@@ -441,7 +439,7 @@ IdentifySystem(void)
 	values[0] = CStringGetTextDatum(sysid);
 
 	/* column 2: timeline */
-	values[1] = Int32GetDatum(ThisTimeLineID);
+	values[1] = Int32GetDatum(currTLI);
 
 	/* column 3: wal location */
 	values[2] = CStringGetTextDatum(xloc);
@@ -537,7 +535,7 @@ ReadReplicationSlot(ReadReplicationSlotCmd *cmd)
 			if (RecoveryInProgress())
 				(void) GetXLogReplayRecPtr(&current_timeline);
 			else
-				current_timeline = ThisTimeLineID;
+				current_timeline = GetWALInsertionTimeLine();
 
 			timeline_history = readTimeLineHistory(current_timeline);
 			slots_position_timeline = tliOfPointInHistory(slot_contents.data.restart_lsn,
@@ -671,6 +669,7 @@ StartReplication(StartReplicationCmd *cmd)
 {
 	StringInfoData buf;
 	XLogRecPtr	FlushPtr;
+	TimeLineID	FlushTLI;
 
 	/* create xlogreader for physical replication */
 	xlogreader =
@@ -710,24 +709,20 @@ StartReplication(StartReplicationCmd *cmd)
 
 	/*
 	 * Select the timeline. If it was given explicitly by the client, use
-	 * that. Otherwise use the timeline of the last replayed record, which is
-	 * kept in ThisTimeLineID.
+	 * that. Otherwise use the timeline of the last replayed record.
 	 */
 	am_cascading_walsender = RecoveryInProgress();
 	if (am_cascading_walsender)
-	{
-		/* this also updates ThisTimeLineID */
-		FlushPtr = GetStandbyFlushRecPtr();
-	}
+		FlushPtr = GetStandbyFlushRecPtr(&FlushTLI);
 	else
-		FlushPtr = GetFlushRecPtr(&ThisTimeLineID);
+		FlushPtr = GetFlushRecPtr(&FlushTLI);
 
 	if (cmd->timeline != 0)
 	{
 		XLogRecPtr	switchpoint;
 
 		sendTimeLine = cmd->timeline;
-		if (sendTimeLine == ThisTimeLineID)
+		if (sendTimeLine == FlushTLI)
 		{
 			sendTimeLineIsHistoric = false;
 			sendTimeLineValidUpto = InvalidXLogRecPtr;
@@ -742,7 +737,7 @@ StartReplication(StartReplicationCmd *cmd)
 			 * Check that the timeline the client requested exists, and the
 			 * requested start location is on that timeline.
 			 */
-			timeLineHistory = readTimeLineHistory(ThisTimeLineID);
+			timeLineHistory = readTimeLineHistory(FlushTLI);
 			switchpoint = tliSwitchPoint(cmd->timeline, timeLineHistory,
 										 &sendTimeLineNextTLI);
 			list_free_deep(timeLineHistory);
@@ -781,7 +776,7 @@ StartReplication(StartReplicationCmd *cmd)
 	}
 	else
 	{
-		sendTimeLine = ThisTimeLineID;
+		sendTimeLine = FlushTLI;
 		sendTimeLineValidUpto = InvalidXLogRecPtr;
 		sendTimeLineIsHistoric = false;
 	}
@@ -909,9 +904,16 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
 	int			count;
 	WALReadError errinfo;
 	XLogSegNo	segno;
+	TimeLineID	currTLI = GetWALInsertionTimeLine();
 
-	XLogReadDetermineTimeline(state, targetPagePtr, reqLen);
-	sendTimeLineIsHistoric = (state->currTLI != ThisTimeLineID);
+	/*
+	 * Since logical decoding is only permitted on a primary server, we know
+	 * that the current timeline ID can't be changing any more. If we did this
+	 * on a standby, we'd have to worry about the values we compute here
+	 * becoming invalid due to a promotion or timeline change.
+	 */
+	XLogReadDetermineTimeline(state, targetPagePtr, reqLen, currTLI);
+	sendTimeLineIsHistoric = (state->currTLI != currTLI);
 	sendTimeLine = state->currTLI;
 	sendTimeLineValidUpto = state->currTLIValidUntil;
 	sendTimeLineNextTLI = state->nextTLI;
@@ -2683,6 +2685,8 @@ XLogSendPhysical(void)
 	}
 	else if (am_cascading_walsender)
 	{
+		TimeLineID	SendRqstTLI;
+
 		/*
 		 * Streaming the latest timeline on a standby.
 		 *
@@ -2702,14 +2706,12 @@ XLogSendPhysical(void)
 		 */
 		bool		becameHistoric = false;
 
-		SendRqstPtr = GetStandbyFlushRecPtr();
+		SendRqstPtr = GetStandbyFlushRecPtr(&SendRqstTLI);
 
 		if (!RecoveryInProgress())
 		{
-			/*
-			 * We have been promoted. RecoveryInProgress() updated
-			 * ThisTimeLineID to the new current timeline.
-			 */
+			/* We have been promoted. */
+			SendRqstTLI = GetWALInsertionTimeLine();
 			am_cascading_walsender = false;
 			becameHistoric = true;
 		}
@@ -2717,10 +2719,9 @@ XLogSendPhysical(void)
 		{
 			/*
 			 * Still a cascading standby. But is the timeline we're sending
-			 * still the one recovery is recovering from? ThisTimeLineID was
-			 * updated by the GetStandbyFlushRecPtr() call above.
+			 * still the one recovery is recovering from?
 			 */
-			if (sendTimeLine != ThisTimeLineID)
+			if (sendTimeLine != SendRqstTLI)
 				becameHistoric = true;
 		}
 
@@ -2733,7 +2734,7 @@ XLogSendPhysical(void)
 			 */
 			List	   *history;
 
-			history = readTimeLineHistory(ThisTimeLineID);
+			history = readTimeLineHistory(SendRqstTLI);
 			sendTimeLineValidUpto = tliSwitchPoint(sendTimeLine, history, &sendTimeLineNextTLI);
 
 			Assert(sendTimeLine < sendTimeLineNextTLI);
@@ -3069,11 +3070,11 @@ WalSndDone(WalSndSendDataCallback send_data)
  * can be sent to the standby. This should only be called when in recovery,
  * ie. we're streaming to a cascaded standby.
  *
- * As a side-effect, ThisTimeLineID is updated to the TLI of the last
+ * As a side-effect, *tli is updated to the TLI of the last
  * replayed WAL record.
  */
 static XLogRecPtr
-GetStandbyFlushRecPtr(void)
+GetStandbyFlushRecPtr(TimeLineID *tli)
 {
 	XLogRecPtr	replayPtr;
 	TimeLineID	replayTLI;
@@ -3090,10 +3091,10 @@ GetStandbyFlushRecPtr(void)
 	receivePtr = GetWalRcvFlushRecPtr(NULL, &receiveTLI);
 	replayPtr = GetXLogReplayRecPtr(&replayTLI);
 
-	ThisTimeLineID = replayTLI;
+	*tli = replayTLI;
 
 	result = replayPtr;
-	if (receiveTLI == ThisTimeLineID && receivePtr > replayPtr)
+	if (receiveTLI == replayTLI && receivePtr > replayPtr)
 		result = receivePtr;
 
 	return result;
diff --git a/src/include/access/xlogutils.h b/src/include/access/xlogutils.h
index a5cb3d322c..eebc91f3a5 100644
--- a/src/include/access/xlogutils.h
+++ b/src/include/access/xlogutils.h
@@ -98,7 +98,9 @@ extern void wal_segment_open(XLogReaderState *state,
 extern void wal_segment_close(XLogReaderState *state);
 
 extern void XLogReadDetermineTimeline(XLogReaderState *state,
-									  XLogRecPtr wantPage, uint32 wantLength);
+									  XLogRecPtr wantPage,
+									  uint32 wantLength,
+									  TimeLineID currTLI);
 
 extern void WALReadRaiseError(WALReadError *errinfo);
 
-- 
2.24.3 (Apple Git-128)

0004-walreceiver.c-Don-t-rely-on-the-global-variable-This.patchapplication/octet-stream; name=0004-walreceiver.c-Don-t-rely-on-the-global-variable-This.patchDownload
From 3b05b248fa9c3b1f0d6b35bfe933ad96947d0300 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 29 Oct 2021 16:57:09 -0400
Subject: [PATCH 04/11] walreceiver.c: Don't rely on the global variable
 ThisTimeLineID.

Instead, pass the TLI around explicitly, as a function parameter.
Since this calls a few xlog.c functions that used ThisTimeLineID,
it was necessary to also change those functions to take a
TimeLineID as a parameter.
---
 src/backend/access/transam/xlog.c     | 52 ++++++++++++++++----------
 src/backend/replication/walreceiver.c | 54 ++++++++++++++++-----------
 src/include/access/xlog.h             |  4 +-
 3 files changed, 66 insertions(+), 44 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6070e0f3b3..91ea1139d8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -917,7 +917,8 @@ static void AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic);
 static bool XLogCheckpointNeeded(XLogSegNo new_segno);
 static void XLogWrite(XLogwrtRqst WriteRqst, bool flexible);
 static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
-								   bool find_free, XLogSegNo max_segno);
+								   bool find_free, XLogSegNo max_segno,
+								   TimeLineID tli);
 static int	XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
 						 XLogSource source, bool notfoundOk);
 static int	XLogFileReadAnyTLI(XLogSegNo segno, int emode, XLogSource source);
@@ -2507,7 +2508,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 							wal_segment_size);
 
 			/* create/use new log file */
-			openLogFile = XLogFileInit(openLogSegNo);
+			openLogFile = XLogFileInit(openLogSegNo, ThisTimeLineID);
 			ReserveExternalFD();
 		}
 
@@ -2622,7 +2623,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 			 */
 			if (finishing_seg)
 			{
-				issue_xlog_fsync(openLogFile, openLogSegNo);
+				issue_xlog_fsync(openLogFile, openLogSegNo, ThisTimeLineID);
 
 				/* signal that we need to wakeup walsenders later */
 				WalSndWakeupRequest();
@@ -2693,7 +2694,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 				ReserveExternalFD();
 			}
 
-			issue_xlog_fsync(openLogFile, openLogSegNo);
+			issue_xlog_fsync(openLogFile, openLogSegNo, ThisTimeLineID);
 		}
 
 		/* signal that we need to wakeup walsenders later */
@@ -3285,7 +3286,8 @@ XLogNeedsFlush(XLogRecPtr record)
  * succeed.  (This is weird, but it's efficient for the callers.)
  */
 static int
-XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
+XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
+					 bool *added, char *path)
 {
 	char		tmppath[MAXPGPATH];
 	PGAlignedXLogBlock zbuffer;
@@ -3294,7 +3296,9 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
 	int			fd;
 	int			save_errno;
 
-	XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size);
+	Assert(logtli != 0);
+
+	XLogFilePath(path, logtli, logsegno, wal_segment_size);
 
 	/*
 	 * Try to use existent file (checkpoint maker may have created it already)
@@ -3438,7 +3442,8 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
 	 * CheckPointSegments.
 	 */
 	max_segno = logsegno + CheckPointSegments;
-	if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno))
+	if (InstallXLogFileSegment(&installed_segno, tmppath, true, max_segno,
+							   logtli))
 	{
 		*added = true;
 		elog(DEBUG2, "done creating and filling new WAL file");
@@ -3470,13 +3475,15 @@ XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
  * in a critical section.
  */
 int
-XLogFileInit(XLogSegNo logsegno)
+XLogFileInit(XLogSegNo logsegno, TimeLineID logtli)
 {
 	bool		ignore_added;
 	char		path[MAXPGPATH];
 	int			fd;
 
-	fd = XLogFileInitInternal(logsegno, &ignore_added, path);
+	Assert(logtli != 0);
+
+	fd = XLogFileInitInternal(logsegno, logtli, &ignore_added, path);
 	if (fd >= 0)
 		return fd;
 
@@ -3618,7 +3625,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
 	/*
 	 * Now move the segment into place with its final name.
 	 */
-	if (!InstallXLogFileSegment(&destsegno, tmppath, false, 0))
+	if (!InstallXLogFileSegment(&destsegno, tmppath, false, 0, ThisTimeLineID))
 		elog(ERROR, "InstallXLogFileSegment should not have failed");
 }
 
@@ -3642,18 +3649,22 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
  * free slot is found between *segno and max_segno. (Ignored when find_free
  * is false.)
  *
+ * tli: The timeline on which the new segment should be installed.
+ *
  * Returns true if the file was installed successfully.  false indicates that
  * 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,
-					   bool find_free, XLogSegNo max_segno)
+					   bool find_free, XLogSegNo max_segno, TimeLineID tli)
 {
 	char		path[MAXPGPATH];
 	struct stat stat_buf;
 
-	XLogFilePath(path, ThisTimeLineID, *segno, wal_segment_size);
+	Assert(tli != 0);
+
+	XLogFilePath(path, tli, *segno, wal_segment_size);
 
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 	if (!XLogCtl->InstallXLogFileSegmentActive)
@@ -3679,7 +3690,7 @@ InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
 				return false;
 			}
 			(*segno)++;
-			XLogFilePath(path, ThisTimeLineID, *segno, wal_segment_size);
+			XLogFilePath(path, tli, *segno, wal_segment_size);
 		}
 	}
 
@@ -3976,7 +3987,7 @@ PreallocXlogFiles(XLogRecPtr endptr)
 	if (offset >= (uint32) (0.75 * wal_segment_size))
 	{
 		_logSegNo++;
-		lf = XLogFileInitInternal(_logSegNo, &added, path);
+		lf = XLogFileInitInternal(_logSegNo, ThisTimeLineID, &added, path);
 		if (lf >= 0)
 			close(lf);
 		if (added)
@@ -4255,7 +4266,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
 		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
 		InstallXLogFileSegment(endlogSegNo, path,
-							   true, recycleSegNo))
+							   true, recycleSegNo, ThisTimeLineID))
 	{
 		ereport(DEBUG2,
 				(errmsg_internal("recycled write-ahead log file \"%s\"",
@@ -5390,7 +5401,7 @@ BootStrapXLOG(void)
 	record->xl_crc = crc;
 
 	/* Create first XLOG segment file */
-	openLogFile = XLogFileInit(1);
+	openLogFile = XLogFileInit(1, ThisTimeLineID);
 
 	/*
 	 * We needn't bother with Reserve/ReleaseExternalFD here, since we'll
@@ -5698,7 +5709,7 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
 		 */
 		int			fd;
 
-		fd = XLogFileInit(startLogSegNo);
+		fd = XLogFileInit(startLogSegNo, ThisTimeLineID);
 
 		if (close(fd) != 0)
 		{
@@ -10858,11 +10869,13 @@ assign_xlog_sync_method(int new_sync_method, void *extra)
  * 'segno' is for error reporting purposes.
  */
 void
-issue_xlog_fsync(int fd, XLogSegNo segno)
+issue_xlog_fsync(int fd, XLogSegNo segno, TimeLineID tli)
 {
 	char	   *msg = NULL;
 	instr_time	start;
 
+	Assert(tli != 0);
+
 	/*
 	 * Quick exit if fsync is disabled or write() has already synced the WAL
 	 * file.
@@ -10911,8 +10924,7 @@ issue_xlog_fsync(int fd, XLogSegNo segno)
 		char		xlogfname[MAXFNAMELEN];
 		int			save_errno = errno;
 
-		XLogFileName(xlogfname, ThisTimeLineID, segno,
-					 wal_segment_size);
+		XLogFileName(xlogfname, tli, segno, wal_segment_size);
 		errno = save_errno;
 		ereport(PANIC,
 				(errcode_for_file_access(),
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b90e5ca98e..7a7eb3784e 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -122,10 +122,12 @@ static StringInfoData incoming_message;
 static void WalRcvFetchTimeLineHistoryFiles(TimeLineID first, TimeLineID last);
 static void WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI);
 static void WalRcvDie(int code, Datum arg);
-static void XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len);
-static void XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr);
-static void XLogWalRcvFlush(bool dying);
-static void XLogWalRcvClose(XLogRecPtr recptr);
+static void XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len,
+								 TimeLineID tli);
+static void XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr,
+							TimeLineID tli);
+static void XLogWalRcvFlush(bool dying, TimeLineID tli);
+static void XLogWalRcvClose(XLogRecPtr recptr, TimeLineID tli);
 static void XLogWalRcvSendReply(bool force, bool requestReply);
 static void XLogWalRcvSendHSFeedback(bool immed);
 static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
@@ -255,7 +257,7 @@ WalReceiverMain(void)
 	pg_atomic_write_u64(&WalRcv->writtenUpto, 0);
 
 	/* Arrange to clean up at walreceiver exit */
-	on_shmem_exit(WalRcvDie, 0);
+	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
 	/* Properly accept or ignore signals the postmaster might send us */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
@@ -394,7 +396,6 @@ WalReceiverMain(void)
 		options.startpoint = startpoint;
 		options.slotname = slotname[0] != '\0' ? slotname : NULL;
 		options.proto.physical.startpointTLI = startpointTLI;
-		ThisTimeLineID = startpointTLI;
 		if (walrcv_startstreaming(wrconn, &options))
 		{
 			if (first_stream)
@@ -462,7 +463,8 @@ WalReceiverMain(void)
 							 */
 							last_recv_timestamp = GetCurrentTimestamp();
 							ping_sent = false;
-							XLogWalRcvProcessMsg(buf[0], &buf[1], len - 1);
+							XLogWalRcvProcessMsg(buf[0], &buf[1], len - 1,
+												 startpointTLI);
 						}
 						else if (len == 0)
 							break;
@@ -487,7 +489,7 @@ WalReceiverMain(void)
 					 * let the startup process and primary server know about
 					 * them.
 					 */
-					XLogWalRcvFlush(false);
+					XLogWalRcvFlush(false, startpointTLI);
 				}
 
 				/* Check if we need to exit the streaming loop. */
@@ -608,7 +610,7 @@ WalReceiverMain(void)
 		{
 			char		xlogfname[MAXFNAMELEN];
 
-			XLogWalRcvFlush(false);
+			XLogWalRcvFlush(false, startpointTLI);
 			XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
 			if (close(recvFile) != 0)
 				ereport(PANIC,
@@ -776,9 +778,12 @@ static void
 WalRcvDie(int code, Datum arg)
 {
 	WalRcvData *walrcv = WalRcv;
+	TimeLineID *startpointTLI_p = (TimeLineID *) DatumGetPointer(arg);
+
+	Assert(*startpointTLI_p != 0);
 
 	/* Ensure that all WAL records received are flushed to disk */
-	XLogWalRcvFlush(true);
+	XLogWalRcvFlush(true, *startpointTLI_p);
 
 	/* Mark ourselves inactive in shared memory */
 	SpinLockAcquire(&walrcv->mutex);
@@ -808,7 +813,7 @@ WalRcvDie(int code, Datum arg)
  * Accept the message from XLOG stream, and process it.
  */
 static void
-XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
+XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len, TimeLineID tli)
 {
 	int			hdrlen;
 	XLogRecPtr	dataStart;
@@ -838,7 +843,7 @@ XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
 
 				buf += hdrlen;
 				len -= hdrlen;
-				XLogWalRcvWrite(buf, len, dataStart);
+				XLogWalRcvWrite(buf, len, dataStart, tli);
 				break;
 			}
 		case 'k':				/* Keepalive */
@@ -875,25 +880,27 @@ XLogWalRcvProcessMsg(unsigned char type, char *buf, Size len)
  * Write XLOG data to disk.
  */
 static void
-XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
+XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr, TimeLineID tli)
 {
 	int			startoff;
 	int			byteswritten;
 
+	Assert(tli != 0);
+
 	while (nbytes > 0)
 	{
 		int			segbytes;
 
 		/* Close the current segment if it's completed */
 		if (recvFile >= 0 && !XLByteInSeg(recptr, recvSegNo, wal_segment_size))
-			XLogWalRcvClose(recptr);
+			XLogWalRcvClose(recptr, tli);
 
 		if (recvFile < 0)
 		{
 			/* Create/use new log file */
 			XLByteToSeg(recptr, recvSegNo, wal_segment_size);
-			recvFile = XLogFileInit(recvSegNo);
-			recvFileTLI = ThisTimeLineID;
+			recvFile = XLogFileInit(recvSegNo, tli);
+			recvFileTLI = tli;
 		}
 
 		/* Calculate the start offset of the received logs */
@@ -946,7 +953,7 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
 	 * segment is received and written.
 	 */
 	if (recvFile >= 0 && !XLByteInSeg(recptr, recvSegNo, wal_segment_size))
-		XLogWalRcvClose(recptr);
+		XLogWalRcvClose(recptr, tli);
 }
 
 /*
@@ -956,13 +963,15 @@ XLogWalRcvWrite(char *buf, Size nbytes, XLogRecPtr recptr)
  * an error, so we skip sending a reply in that case.
  */
 static void
-XLogWalRcvFlush(bool dying)
+XLogWalRcvFlush(bool dying, TimeLineID tli)
 {
+	Assert(tli != 0);
+
 	if (LogstreamResult.Flush < LogstreamResult.Write)
 	{
 		WalRcvData *walrcv = WalRcv;
 
-		issue_xlog_fsync(recvFile, recvSegNo);
+		issue_xlog_fsync(recvFile, recvSegNo, tli);
 
 		LogstreamResult.Flush = LogstreamResult.Write;
 
@@ -972,7 +981,7 @@ XLogWalRcvFlush(bool dying)
 		{
 			walrcv->latestChunkStart = walrcv->flushedUpto;
 			walrcv->flushedUpto = LogstreamResult.Flush;
-			walrcv->receivedTLI = ThisTimeLineID;
+			walrcv->receivedTLI = tli;
 		}
 		SpinLockRelease(&walrcv->mutex);
 
@@ -1009,17 +1018,18 @@ XLogWalRcvFlush(bool dying)
  * Create an archive notification file since the segment is known completed.
  */
 static void
-XLogWalRcvClose(XLogRecPtr recptr)
+XLogWalRcvClose(XLogRecPtr recptr, TimeLineID tli)
 {
 	char		xlogfname[MAXFNAMELEN];
 
 	Assert(recvFile >= 0 && !XLByteInSeg(recptr, recvSegNo, wal_segment_size));
+	Assert(tli != 0);
 
 	/*
 	 * fsync() and close current file before we switch to next one. We would
 	 * otherwise have to reopen this file to fsync it later
 	 */
-	XLogWalRcvFlush(false);
+	XLogWalRcvFlush(false, tli);
 
 	XLogFileName(xlogfname, recvFileTLI, recvSegNo, wal_segment_size);
 
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 72417f44b6..22f1d37fb2 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -262,7 +262,7 @@ extern XLogRecPtr XLogInsertRecord(struct XLogRecData *rdata,
 extern void XLogFlush(XLogRecPtr RecPtr);
 extern bool XLogBackgroundFlush(void);
 extern bool XLogNeedsFlush(XLogRecPtr RecPtr);
-extern int	XLogFileInit(XLogSegNo segno);
+extern int	XLogFileInit(XLogSegNo segno, TimeLineID tli);
 extern int	XLogFileOpen(XLogSegNo segno);
 
 extern void CheckXLogRemoved(XLogSegNo segno, TimeLineID tli);
@@ -274,7 +274,7 @@ extern void xlog_redo(XLogReaderState *record);
 extern void xlog_desc(StringInfo buf, XLogReaderState *record);
 extern const char *xlog_identify(uint8 info);
 
-extern void issue_xlog_fsync(int fd, XLogSegNo segno);
+extern void issue_xlog_fsync(int fd, XLogSegNo segno, TimeLineID tli);
 
 extern bool RecoveryInProgress(void);
 extern RecoveryState GetRecoveryState(void);
-- 
2.24.3 (Apple Git-128)

0002-Add-GetWALInsertionTimeLine-also-return-it-via-GetFl.patchapplication/octet-stream; name=0002-Add-GetWALInsertionTimeLine-also-return-it-via-GetFl.patchDownload
From c00f605ab03aec7df21eff0f098751837f2ba935 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 29 Oct 2021 16:03:16 -0400
Subject: [PATCH 02/11] Add GetWALInsertionTimeLine(); also return it via
 GetFlushRecPtr().

Having lots of code that relies on the ThisTimeLineID global variable
is problematic, but to have any chance of fixing that, we must provide
another way for code outside xlog.c to get the relevant value. This
commit adds infrastructure for doing that on a system that is in
normal running, but doesn't do anything about the case where the
system is in recovery.

Here, we provide two different methods for getting the timeline of
a system in normal running. First, a number of the places where we
call GetFlushRecPtr() need the timeline as well, so provide it via
an out parameter. Second, for other places that just need the
timeline and not any LSN, add a GetWALInsertionTimeLine() function.

Future commits will take care of actually removing references to
ThisTimeLineID; this commit is just infrastructure.
---
 src/backend/access/transam/xlog.c             | 22 ++++++++++++++++++-
 src/backend/access/transam/xlogfuncs.c        |  2 +-
 src/backend/access/transam/xlogutils.c        |  6 +----
 .../replication/logical/logicalfuncs.c        |  2 +-
 src/backend/replication/logical/worker.c      |  2 +-
 src/backend/replication/slotfuncs.c           |  2 +-
 src/backend/replication/walsender.c           | 14 ++++++------
 src/include/access/xlog.h                     |  3 ++-
 8 files changed, 35 insertions(+), 18 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f547efd294..6070e0f3b3 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -8695,15 +8695,35 @@ GetInsertRecPtr(void)
  * position known to be fsync'd to disk.
  */
 XLogRecPtr
-GetFlushRecPtr(void)
+GetFlushRecPtr(TimeLineID *insertTLI)
 {
 	SpinLockAcquire(&XLogCtl->info_lck);
 	LogwrtResult = XLogCtl->LogwrtResult;
 	SpinLockRelease(&XLogCtl->info_lck);
 
+	/*
+	 * If we're writing and flushing WAL, the time line can't be changing,
+	 * so no lock is required.
+	 */
+	if (insertTLI)
+		*insertTLI = XLogCtl->ThisTimeLineID;
+
 	return LogwrtResult.Flush;
 }
 
+/*
+ * GetWALInsertionTimeLine -- Returns the current timeline of a system that
+ * is not in recovery.
+ */
+TimeLineID
+GetWALInsertionTimeLine(void)
+{
+	Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
+
+	/* Since the value can't be changing, no lock is required. */
+	return XLogCtl->ThisTimeLineID;
+}
+
 /*
  * GetLastImportantRecPtr -- Returns the LSN of the last important record
  * inserted. All records not explicitly marked as unimportant are considered
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index b98deb72ec..5bebcc50dd 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -382,7 +382,7 @@ pg_current_wal_flush_lsn(PG_FUNCTION_ARGS)
 				 errmsg("recovery is in progress"),
 				 errhint("WAL control functions cannot be executed during recovery.")));
 
-	current_recptr = GetFlushRecPtr();
+	current_recptr = GetFlushRecPtr(NULL);
 
 	PG_RETURN_LSN(current_recptr);
 }
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 88a1bfd939..c40500a6f2 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -862,13 +862,9 @@ read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
 		/*
 		 * Determine the limit of xlog we can currently read to, and what the
 		 * most recent timeline is.
-		 *
-		 * RecoveryInProgress() will update ThisTimeLineID when it first
-		 * notices recovery finishes, so we only have to maintain it for the
-		 * local process until recovery ends.
 		 */
 		if (!RecoveryInProgress())
-			read_upto = GetFlushRecPtr();
+			read_upto = GetFlushRecPtr(&ThisTimeLineID);
 		else
 			read_upto = GetXLogReplayRecPtr(&ThisTimeLineID);
 		tli = ThisTimeLineID;
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 5a7fae4a87..2609a0a710 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -211,7 +211,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	 * Compute the current end-of-wal.
 	 */
 	if (!RecoveryInProgress())
-		end_of_wal = GetFlushRecPtr();
+		end_of_wal = GetFlushRecPtr(NULL);
 	else
 		end_of_wal = GetXLogReplayRecPtr(NULL);
 
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 8d96c926b4..0bd5d0ee5e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -2435,7 +2435,7 @@ get_flush_position(XLogRecPtr *write, XLogRecPtr *flush,
 				   bool *have_pending_txes)
 {
 	dlist_mutable_iter iter;
-	XLogRecPtr	local_flush = GetFlushRecPtr();
+	XLogRecPtr	local_flush = GetFlushRecPtr(NULL);
 
 	*write = InvalidXLogRecPtr;
 	*flush = InvalidXLogRecPtr;
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 877a006d50..a80298ba53 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -625,7 +625,7 @@ pg_replication_slot_advance(PG_FUNCTION_ARGS)
 	 * target position accordingly.
 	 */
 	if (!RecoveryInProgress())
-		moveto = Min(moveto, GetFlushRecPtr());
+		moveto = Min(moveto, GetFlushRecPtr(NULL));
 	else
 		moveto = Min(moveto, GetXLogReplayRecPtr(NULL));
 
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d9ab6d6de2..d09bffaa9d 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -402,7 +402,7 @@ IdentifySystem(void)
 		logptr = GetStandbyFlushRecPtr();
 	}
 	else
-		logptr = GetFlushRecPtr();
+		logptr = GetFlushRecPtr(&ThisTimeLineID);
 
 	snprintf(xloc, sizeof(xloc), "%X/%X", LSN_FORMAT_ARGS(logptr));
 
@@ -720,7 +720,7 @@ StartReplication(StartReplicationCmd *cmd)
 		FlushPtr = GetStandbyFlushRecPtr();
 	}
 	else
-		FlushPtr = GetFlushRecPtr();
+		FlushPtr = GetFlushRecPtr(&ThisTimeLineID);
 
 	if (cmd->timeline != 0)
 	{
@@ -1487,7 +1487,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 
 	/* Get a more recent flush pointer. */
 	if (!RecoveryInProgress())
-		RecentFlushPtr = GetFlushRecPtr();
+		RecentFlushPtr = GetFlushRecPtr(NULL);
 	else
 		RecentFlushPtr = GetXLogReplayRecPtr(NULL);
 
@@ -1521,7 +1521,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 
 		/* Update our idea of the currently flushed position. */
 		if (!RecoveryInProgress())
-			RecentFlushPtr = GetFlushRecPtr();
+			RecentFlushPtr = GetFlushRecPtr(NULL);
 		else
 			RecentFlushPtr = GetXLogReplayRecPtr(NULL);
 
@@ -2756,7 +2756,7 @@ XLogSendPhysical(void)
 		 * primary: if the primary subsequently crashes and restarts, standbys
 		 * must not have applied any WAL that got lost on the primary.
 		 */
-		SendRqstPtr = GetFlushRecPtr();
+		SendRqstPtr = GetFlushRecPtr(NULL);
 	}
 
 	/*
@@ -2997,9 +2997,9 @@ XLogSendLogical(void)
 	 * we only need to update flushPtr if EndRecPtr is past it.
 	 */
 	if (flushPtr == InvalidXLogRecPtr)
-		flushPtr = GetFlushRecPtr();
+		flushPtr = GetFlushRecPtr(NULL);
 	else if (logical_decoding_ctx->reader->EndRecPtr >= flushPtr)
-		flushPtr = GetFlushRecPtr();
+		flushPtr = GetFlushRecPtr(NULL);
 
 	/* If EndRecPtr is still past our flushPtr, it means we caught up. */
 	if (logical_decoding_ctx->reader->EndRecPtr >= flushPtr)
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 5e2c94a05f..72417f44b6 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -312,7 +312,8 @@ extern void UpdateFullPageWrites(void);
 extern void GetFullPageWriteInfo(XLogRecPtr *RedoRecPtr_p, bool *doPageWrites_p);
 extern XLogRecPtr GetRedoRecPtr(void);
 extern XLogRecPtr GetInsertRecPtr(void);
-extern XLogRecPtr GetFlushRecPtr(void);
+extern XLogRecPtr GetFlushRecPtr(TimeLineID *insertTLI);
+extern TimeLineID GetWALInsertionTimeLine(void);
 extern XLogRecPtr GetLastImportantRecPtr(void);
 extern void RemovePromoteSignalFiles(void);
 
-- 
2.24.3 (Apple Git-128)

0001-Don-t-set-ThisTimeLineID-when-there-s-no-reason-to-d.patchapplication/octet-stream; name=0001-Don-t-set-ThisTimeLineID-when-there-s-no-reason-to-d.patchDownload
From 082b1b87a32b4690644014bcb43a492551eae01e Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 27 Oct 2021 17:03:44 -0400
Subject: [PATCH 01/11] Don't set ThisTimeLineID when there's no reason to do
 so.

In slotfuncs.c, pg_replication_slot_advance() needs to determine
the LSN up to which the slot should be advanced, but that doesn't
require us to update ThisTimeLineID, because none of the code called
from here depends on it. If the replication slot is logical,
pg_logical_replication_slot_advance will call read_local_xlog_page,
which does use ThisTimeLineID, but also takes care of making sure
it's up to date. If the replication slot is physical, the timeline
isn't used for anything at all.

In logicalfuncs.c, pg_logical_slot_get_changes_guts() has the same
issue: the only code we're going to run that cares about timelines
is in or downstream of read_local_xlog_page, which already makes
sure that the correct value gets set. Hence, don't do it here.
---
 src/backend/replication/logical/logicalfuncs.c | 5 ++---
 src/backend/replication/slotfuncs.c            | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index e59939aad1..5a7fae4a87 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -208,13 +208,12 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin
 	rsinfo->setDesc = p->tupdesc;
 
 	/*
-	 * Compute the current end-of-wal and maintain ThisTimeLineID.
-	 * RecoveryInProgress() will update ThisTimeLineID on promotion.
+	 * Compute the current end-of-wal.
 	 */
 	if (!RecoveryInProgress())
 		end_of_wal = GetFlushRecPtr();
 	else
-		end_of_wal = GetXLogReplayRecPtr(&ThisTimeLineID);
+		end_of_wal = GetXLogReplayRecPtr(NULL);
 
 	ReplicationSlotAcquire(NameStr(*name), true);
 
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 17df99c2ac..877a006d50 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -627,7 +627,7 @@ pg_replication_slot_advance(PG_FUNCTION_ARGS)
 	if (!RecoveryInProgress())
 		moveto = Min(moveto, GetFlushRecPtr());
 	else
-		moveto = Min(moveto, GetXLogReplayRecPtr(&ThisTimeLineID));
+		moveto = Min(moveto, GetXLogReplayRecPtr(NULL));
 
 	/* Acquire the slot so we "own" it */
 	ReplicationSlotAcquire(NameStr(*slotname), true);
-- 
2.24.3 (Apple Git-128)

0006-xlog.c-Change-assorted-functions-to-take-an-explicit.patchapplication/octet-stream; name=0006-xlog.c-Change-assorted-functions-to-take-an-explicit.patchDownload
From e88517e6f5993e340fbfb69aa63081640491b20d Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 10:44:46 -0400
Subject: [PATCH 06/11] xlog.c: Change assorted functions to take an explicit
 TimeLineID.

It is confusing to have functions whose behavior depends on the
value of a global variable, so where possible, make the caller
pass the timeline as an explicit argument.
---
 src/backend/access/transam/xlog.c | 145 +++++++++++++++++-------------
 src/include/access/xlog.h         |   2 +-
 2 files changed, 82 insertions(+), 65 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 801175c177..16f90769c2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -889,9 +889,11 @@ static MemoryContext walDebugCxt = NULL;
 
 static void readRecoverySignalFile(void);
 static void validateRecoveryParameters(void);
-static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
+static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog,
+								TimeLineID newTLI);
 static void CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI,
-										XLogRecPtr EndOfLog);
+										XLogRecPtr EndOfLog,
+										TimeLineID newTLI);
 static bool recoveryStopsBefore(XLogReaderState *record);
 static bool recoveryStopsAfter(XLogReaderState *record);
 static char *getRecoveryStopReason(void);
@@ -903,7 +905,7 @@ static void SetCurrentChunkStartTime(TimestampTz xtime);
 static void CheckRequiredParameterValues(void);
 static void XLogReportParameters(void);
 static void checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI,
-								TimeLineID prevTLI);
+								TimeLineID prevTLI, TimeLineID replayTLI);
 static void VerifyOverwriteContrecord(xl_overwrite_contrecord *xlrec,
 									  XLogReaderState *state);
 static int LocalSetXLogInsertAllowed(void);
@@ -913,9 +915,10 @@ static void CheckPointGuts(XLogRecPtr checkPointRedo, int flags);
 static void KeepLogSeg(XLogRecPtr recptr, XLogSegNo *logSegNo);
 static XLogRecPtr XLogGetReplicationSlotMinimumLSN(void);
 
-static void AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic);
+static void AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli,
+								  bool opportunistic);
 static bool XLogCheckpointNeeded(XLogSegNo new_segno);
-static void XLogWrite(XLogwrtRqst WriteRqst, bool flexible);
+static void XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible);
 static bool InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
 								   bool find_free, XLogSegNo max_segno,
 								   TimeLineID tli);
@@ -929,11 +932,12 @@ static bool WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 static void XLogShutdownWalRcv(void);
 static int	emode_for_corrupt_record(int emode, XLogRecPtr RecPtr);
 static void XLogFileClose(void);
-static void PreallocXlogFiles(XLogRecPtr endptr);
+static void PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli);
 static void RemoveTempXlogFiles(void);
-static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr);
+static void RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr,
+							   XLogRecPtr endptr, TimeLineID insertTLI);
 static void RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-						   XLogSegNo *endlogSegNo);
+						   XLogSegNo *endlogSegNo, TimeLineID insertTLI);
 static void UpdateLastRemovedPtr(char *filename);
 static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
@@ -968,13 +972,14 @@ static int	get_sync_bit(int method);
 
 static void CopyXLogRecordToWAL(int write_len, bool isLogSwitch,
 								XLogRecData *rdata,
-								XLogRecPtr StartPos, XLogRecPtr EndPos);
+								XLogRecPtr StartPos, XLogRecPtr EndPos,
+								TimeLineID tli);
 static void ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos,
 									  XLogRecPtr *EndPos, XLogRecPtr *PrevPtr);
 static bool ReserveXLogSwitch(XLogRecPtr *StartPos, XLogRecPtr *EndPos,
 							  XLogRecPtr *PrevPtr);
 static XLogRecPtr WaitXLogInsertionsToFinish(XLogRecPtr upto);
-static char *GetXLogBuffer(XLogRecPtr ptr);
+static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
 static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
 static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
 static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
@@ -1138,7 +1143,7 @@ XLogInsertRecord(XLogRecData *rdata,
 		 * inserted. Copy the record in the space reserved.
 		 */
 		CopyXLogRecordToWAL(rechdr->xl_tot_len, isLogSwitch, rdata,
-							StartPos, EndPos);
+							StartPos, EndPos, ThisTimeLineID);
 
 		/*
 		 * Unless record is flagged as not important, update LSN of last
@@ -1522,7 +1527,7 @@ checkXLogConsistency(XLogReaderState *record)
  */
 static void
 CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,
-					XLogRecPtr StartPos, XLogRecPtr EndPos)
+					XLogRecPtr StartPos, XLogRecPtr EndPos, TimeLineID tli)
 {
 	char	   *currpos;
 	int			freespace;
@@ -1535,7 +1540,7 @@ CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,
 	 * inserting to.
 	 */
 	CurrPos = StartPos;
-	currpos = GetXLogBuffer(CurrPos);
+	currpos = GetXLogBuffer(CurrPos, tli);
 	freespace = INSERT_FREESPACE(CurrPos);
 
 	/*
@@ -1572,7 +1577,7 @@ CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,
 			 * page was initialized, in AdvanceXLInsertBuffer, and we're the
 			 * only backend that needs to set the contrecord flag.
 			 */
-			currpos = GetXLogBuffer(CurrPos);
+			currpos = GetXLogBuffer(CurrPos, tli);
 			pagehdr = (XLogPageHeader) currpos;
 			pagehdr->xlp_rem_len = write_len - written;
 			pagehdr->xlp_info |= XLP_FIRST_IS_CONTRECORD;
@@ -1645,7 +1650,7 @@ CopyXLogRecordToWAL(int write_len, bool isLogSwitch, XLogRecData *rdata,
 			 * (which itself calls the two methods we need) to get the pointer
 			 * and zero most of the page.  Then we just zero the page header.
 			 */
-			currpos = GetXLogBuffer(CurrPos);
+			currpos = GetXLogBuffer(CurrPos, tli);
 			MemSet(currpos, 0, SizeOfXLogShortPHD);
 
 			CurrPos += XLOG_BLCKSZ;
@@ -1897,7 +1902,7 @@ WaitXLogInsertionsToFinish(XLogRecPtr upto)
  * later, because older buffers might be recycled already)
  */
 static char *
-GetXLogBuffer(XLogRecPtr ptr)
+GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli)
 {
 	int			idx;
 	XLogRecPtr	endptr;
@@ -1973,7 +1978,7 @@ GetXLogBuffer(XLogRecPtr ptr)
 
 		WALInsertLockUpdateInsertingAt(initializedUpto);
 
-		AdvanceXLInsertBuffer(ptr, false);
+		AdvanceXLInsertBuffer(ptr, tli, false);
 		endptr = XLogCtl->xlblocks[idx];
 
 		if (expectedEndPtr != endptr)
@@ -2135,7 +2140,7 @@ XLogRecPtrToBytePos(XLogRecPtr ptr)
  * initialized properly.
  */
 static void
-AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic)
+AdvanceXLInsertBuffer(XLogRecPtr upto, TimeLineID tli, bool opportunistic)
 {
 	XLogCtlInsert *Insert = &XLogCtl->Insert;
 	int			nextidx;
@@ -2208,7 +2213,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic)
 					TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_START();
 					WriteRqst.Write = OldPageRqstPtr;
 					WriteRqst.Flush = 0;
-					XLogWrite(WriteRqst, false);
+					XLogWrite(WriteRqst, tli, false);
 					LWLockRelease(WALWriteLock);
 					WalStats.m_wal_buffers_full++;
 					TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE();
@@ -2242,7 +2247,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic)
 		NewPage->xlp_magic = XLOG_PAGE_MAGIC;
 
 		/* NewPage->xlp_info = 0; */	/* done by memset */
-		NewPage->xlp_tli = ThisTimeLineID;
+		NewPage->xlp_tli = tli;
 		NewPage->xlp_pageaddr = NewPageBeginPtr;
 
 		/* NewPage->xlp_rem_len = 0; */	/* done by memset */
@@ -2438,7 +2443,7 @@ XLogCheckpointNeeded(XLogSegNo new_segno)
  * write.
  */
 static void
-XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
+XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
 {
 	bool		ispartialpage;
 	bool		last_iteration;
@@ -2508,7 +2513,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 							wal_segment_size);
 
 			/* create/use new log file */
-			openLogFile = XLogFileInit(openLogSegNo, ThisTimeLineID);
+			openLogFile = XLogFileInit(openLogSegNo, tli);
 			ReserveExternalFD();
 		}
 
@@ -2517,7 +2522,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 		{
 			XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,
 							wal_segment_size);
-			openLogFile = XLogFileOpen(openLogSegNo);
+			openLogFile = XLogFileOpen(openLogSegNo, tli);
 			ReserveExternalFD();
 		}
 
@@ -2592,7 +2597,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 						continue;
 
 					save_errno = errno;
-					XLogFileName(xlogfname, ThisTimeLineID, openLogSegNo,
+					XLogFileName(xlogfname, tli, openLogSegNo,
 								 wal_segment_size);
 					errno = save_errno;
 					ereport(PANIC,
@@ -2623,7 +2628,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 			 */
 			if (finishing_seg)
 			{
-				issue_xlog_fsync(openLogFile, openLogSegNo, ThisTimeLineID);
+				issue_xlog_fsync(openLogFile, openLogSegNo, tli);
 
 				/* signal that we need to wakeup walsenders later */
 				WalSndWakeupRequest();
@@ -2631,7 +2636,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 				LogwrtResult.Flush = LogwrtResult.Write;	/* end of page */
 
 				if (XLogArchivingActive())
-					XLogArchiveNotifySeg(openLogSegNo, ThisTimeLineID);
+					XLogArchiveNotifySeg(openLogSegNo, tli);
 
 				XLogCtl->lastSegSwitchTime = (pg_time_t) time(NULL);
 				XLogCtl->lastSegSwitchLSN = LogwrtResult.Flush;
@@ -2690,11 +2695,11 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 			{
 				XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,
 								wal_segment_size);
-				openLogFile = XLogFileOpen(openLogSegNo);
+				openLogFile = XLogFileOpen(openLogSegNo, tli);
 				ReserveExternalFD();
 			}
 
-			issue_xlog_fsync(openLogFile, openLogSegNo, ThisTimeLineID);
+			issue_xlog_fsync(openLogFile, openLogSegNo, tli);
 		}
 
 		/* signal that we need to wakeup walsenders later */
@@ -3010,7 +3015,7 @@ XLogFlush(XLogRecPtr record)
 		WriteRqst.Write = insertpos;
 		WriteRqst.Flush = insertpos;
 
-		XLogWrite(WriteRqst, false);
+		XLogWrite(WriteRqst, ThisTimeLineID, false);
 
 		LWLockRelease(WALWriteLock);
 		/* done */
@@ -3177,7 +3182,7 @@ XLogBackgroundFlush(void)
 	if (WriteRqst.Write > LogwrtResult.Write ||
 		WriteRqst.Flush > LogwrtResult.Flush)
 	{
-		XLogWrite(WriteRqst, flexible);
+		XLogWrite(WriteRqst, ThisTimeLineID, flexible);
 	}
 	LWLockRelease(WALWriteLock);
 
@@ -3190,7 +3195,7 @@ XLogBackgroundFlush(void)
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
 	 */
-	AdvanceXLInsertBuffer(InvalidXLogRecPtr, true);
+	AdvanceXLInsertBuffer(InvalidXLogRecPtr, ThisTimeLineID, true);
 
 	/*
 	 * If we determined that we need to write data, but somebody else
@@ -3512,7 +3517,8 @@ XLogFileInit(XLogSegNo logsegno, TimeLineID logtli)
  * emplacing a bogus file.
  */
 static void
-XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
+XLogFileCopy(TimeLineID destTLI, XLogSegNo destsegno,
+			 TimeLineID srcTLI, XLogSegNo srcsegno,
 			 int upto)
 {
 	char		path[MAXPGPATH];
@@ -3625,7 +3631,7 @@ XLogFileCopy(XLogSegNo destsegno, TimeLineID srcTLI, XLogSegNo srcsegno,
 	/*
 	 * Now move the segment into place with its final name.
 	 */
-	if (!InstallXLogFileSegment(&destsegno, tmppath, false, 0, ThisTimeLineID))
+	if (!InstallXLogFileSegment(&destsegno, tmppath, false, 0, destTLI))
 		elog(ERROR, "InstallXLogFileSegment should not have failed");
 }
 
@@ -3714,12 +3720,12 @@ InstallXLogFileSegment(XLogSegNo *segno, char *tmppath,
  * Open a pre-existing logfile segment for writing.
  */
 int
-XLogFileOpen(XLogSegNo segno)
+XLogFileOpen(XLogSegNo segno, TimeLineID tli)
 {
 	char		path[MAXPGPATH];
 	int			fd;
 
-	XLogFilePath(path, ThisTimeLineID, segno, wal_segment_size);
+	XLogFilePath(path, tli, segno, wal_segment_size);
 
 	fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method));
 	if (fd < 0)
@@ -3971,7 +3977,7 @@ XLogFileClose(void)
  * reporting and resource reclamation.)
  */
 static void
-PreallocXlogFiles(XLogRecPtr endptr)
+PreallocXlogFiles(XLogRecPtr endptr, TimeLineID tli)
 {
 	XLogSegNo	_logSegNo;
 	int			lf;
@@ -3987,7 +3993,7 @@ PreallocXlogFiles(XLogRecPtr endptr)
 	if (offset >= (uint32) (0.75 * wal_segment_size))
 	{
 		_logSegNo++;
-		lf = XLogFileInitInternal(_logSegNo, ThisTimeLineID, &added, path);
+		lf = XLogFileInitInternal(_logSegNo, tli, &added, path);
 		if (lf >= 0)
 			close(lf);
 		if (added)
@@ -4104,9 +4110,13 @@ RemoveTempXlogFiles(void)
  * endptr is current (or recent) end of xlog, and lastredoptr is the
  * redo pointer of the last checkpoint. These are used to determine
  * whether we want to recycle rather than delete no-longer-wanted log files.
+ *
+ * insertTLI is the current timeline for XLOG insertion. Any recycled
+ * segments should be reused for this timeline.
  */
 static void
-RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr)
+RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
+				   TimeLineID insertTLI)
 {
 	DIR		   *xldir;
 	struct dirent *xlde;
@@ -4155,7 +4165,8 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr)
 				/* Update the last removed location in shared memory first */
 				UpdateLastRemovedPtr(xlde->d_name);
 
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo);
+				RemoveXlogFile(xlde->d_name, recycleSegNo, &endlogSegNo,
+							   insertTLI);
 			}
 		}
 	}
@@ -4227,7 +4238,8 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
 			 * - but seems safer to let them be archived and removed later.
 			 */
 			if (!XLogArchiveIsReady(xlde->d_name))
-				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo);
+				RemoveXlogFile(xlde->d_name, recycleSegNo, &endLogSegNo,
+							   newTLI);
 		}
 	}
 
@@ -4243,10 +4255,13 @@ RemoveNonParentXlogFiles(XLogRecPtr switchpoint, TimeLineID newTLI)
  *
  * endlogSegNo gets incremented if the segment is recycled so as it is not
  * checked again with future callers of this function.
+ *
+ * insertTLI is the current timeline for XLOG insertion. Any recycled segments
+ * should be used for this timeline.
  */
 static void
 RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
-			   XLogSegNo *endlogSegNo)
+			   XLogSegNo *endlogSegNo, TimeLineID insertTLI)
 {
 	char		path[MAXPGPATH];
 #ifdef WIN32
@@ -4266,7 +4281,7 @@ RemoveXlogFile(const char *segname, XLogSegNo recycleSegNo,
 		XLogCtl->InstallXLogFileSegmentActive &&	/* callee rechecks this */
 		lstat(path, &statbuf) == 0 && S_ISREG(statbuf.st_mode) &&
 		InstallXLogFileSegment(endlogSegNo, path,
-							   true, recycleSegNo, ThisTimeLineID))
+							   true, recycleSegNo, insertTLI))
 	{
 		ereport(DEBUG2,
 				(errmsg_internal("recycled write-ahead log file \"%s\"",
@@ -5645,14 +5660,14 @@ validateRecoveryParameters(void)
  * Exit archive-recovery state
  */
 static void
-exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
+exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog, TimeLineID newTLI)
 {
 	char		xlogfname[MAXFNAMELEN];
 	XLogSegNo	endLogSegNo;
 	XLogSegNo	startLogSegNo;
 
 	/* we always switch to a new timeline after archive recovery */
-	Assert(endTLI != ThisTimeLineID);
+	Assert(endTLI != newTLI);
 
 	/*
 	 * We are no longer in archive recovery state.
@@ -5698,7 +5713,7 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
 		 * considerations. But we should be just as tense as XLogFileInit to
 		 * avoid emplacing a bogus file.
 		 */
-		XLogFileCopy(endLogSegNo, endTLI, endLogSegNo,
+		XLogFileCopy(newTLI, endLogSegNo, endTLI, endLogSegNo,
 					 XLogSegmentOffset(endOfLog, wal_segment_size));
 	}
 	else
@@ -5709,15 +5724,14 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
 		 */
 		int			fd;
 
-		fd = XLogFileInit(startLogSegNo, ThisTimeLineID);
+		fd = XLogFileInit(startLogSegNo, newTLI);
 
 		if (close(fd) != 0)
 		{
 			char		xlogfname[MAXFNAMELEN];
 			int			save_errno = errno;
 
-			XLogFileName(xlogfname, ThisTimeLineID, startLogSegNo,
-						 wal_segment_size);
+			XLogFileName(xlogfname, newTLI, startLogSegNo, wal_segment_size);
 			errno = save_errno;
 			ereport(ERROR,
 					(errcode_for_file_access(),
@@ -5729,7 +5743,7 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
 	 * Let's just make real sure there are not .ready or .done flags posted
 	 * for the new segment.
 	 */
-	XLogFileName(xlogfname, ThisTimeLineID, startLogSegNo, wal_segment_size);
+	XLogFileName(xlogfname, newTLI, startLogSegNo, wal_segment_size);
 	XLogArchiveCleanup(xlogfname);
 
 	/*
@@ -5750,7 +5764,8 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
  * Perform cleanup actions at the conclusion of archive recovery.
  */
 static void
-CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog)
+CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
+							TimeLineID newTLI)
 {
 	/*
 	 * Execute the recovery_end_command, if any.
@@ -5768,7 +5783,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog)
 	 * files containing garbage. In any case, they are not part of the new
 	 * timeline's history so we don't need them.
 	 */
-	RemoveNonParentXlogFiles(EndOfLog, ThisTimeLineID);
+	RemoveNonParentXlogFiles(EndOfLog, newTLI);
 
 	/*
 	 * If the switch happened in the middle of a segment, what to do with the
@@ -7635,7 +7650,8 @@ StartupXLOG(void)
 					if (newTLI != ThisTimeLineID)
 					{
 						/* Check that it's OK to switch to this TLI */
-						checkTimeLineSwitch(EndRecPtr, newTLI, prevTLI);
+						checkTimeLineSwitch(EndRecPtr, newTLI, prevTLI,
+											ThisTimeLineID);
 
 						/* Following WAL records should be run with new TLI */
 						ThisTimeLineID = newTLI;
@@ -7948,7 +7964,7 @@ StartupXLOG(void)
 		 * (Note that we also have a copy of the last block of the old WAL in
 		 * readBuf; we will use that below.)
 		 */
-		exitArchiveRecovery(EndOfLogTLI, EndOfLog);
+		exitArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
 
 		/*
 		 * Write the timeline history file, and have it archived. After this
@@ -8046,7 +8062,7 @@ StartupXLOG(void)
 	/*
 	 * Preallocate additional log files, if wanted.
 	 */
-	PreallocXlogFiles(EndOfLog);
+	PreallocXlogFiles(EndOfLog, ThisTimeLineID);
 
 	/*
 	 * Okay, we're officially UP.
@@ -8127,7 +8143,7 @@ StartupXLOG(void)
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
 	if (ArchiveRecoveryRequested)
-		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog);
+		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
 
 	/*
 	 * Local WAL inserts enabled, so it's time to finish initialization of
@@ -9440,14 +9456,14 @@ CreateCheckPoint(int flags)
 		KeepLogSeg(recptr, &_logSegNo);
 	}
 	_logSegNo--;
-	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr);
+	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr, ThisTimeLineID);
 
 	/*
 	 * Make more log segments if needed.  (Do this after recycling old log
 	 * segments, since that may supply some of the needed files.)
 	 */
 	if (!shutdown)
-		PreallocXlogFiles(recptr);
+		PreallocXlogFiles(recptr, ThisTimeLineID);
 
 	/*
 	 * Truncate pg_subtrans if possible.  We can throw away all data before
@@ -9848,13 +9864,13 @@ CreateRestartPoint(int flags)
 	if (RecoveryInProgress())
 		ThisTimeLineID = replayTLI;
 
-	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr);
+	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, ThisTimeLineID);
 
 	/*
 	 * Make more log segments if needed.  (Do this after recycling old log
 	 * segments, since that may supply some of the needed files.)
 	 */
-	PreallocXlogFiles(endptr);
+	PreallocXlogFiles(endptr, ThisTimeLineID);
 
 	/*
 	 * ThisTimeLineID is normally not set when we're still in recovery.
@@ -10271,22 +10287,23 @@ UpdateFullPageWrites(void)
  * replay. (Currently, timeline can only change at a shutdown checkpoint).
  */
 static void
-checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI, TimeLineID prevTLI)
+checkTimeLineSwitch(XLogRecPtr lsn, TimeLineID newTLI, TimeLineID prevTLI,
+					TimeLineID replayTLI)
 {
 	/* Check that the record agrees on what the current (old) timeline is */
-	if (prevTLI != ThisTimeLineID)
+	if (prevTLI != replayTLI)
 		ereport(PANIC,
 				(errmsg("unexpected previous timeline ID %u (current timeline ID %u) in checkpoint record",
-						prevTLI, ThisTimeLineID)));
+						prevTLI, replayTLI)));
 
 	/*
 	 * 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))
+	if (newTLI < replayTLI || !tliInHistory(newTLI, expectedTLEs))
 		ereport(PANIC,
 				(errmsg("unexpected timeline ID %u (after %u) in checkpoint record",
-						newTLI, ThisTimeLineID)));
+						newTLI, replayTLI)));
 
 	/*
 	 * If we have not yet reached min recovery point, and we're about to
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 51a672b703..2b42082ffe 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -261,7 +261,7 @@ extern void XLogFlush(XLogRecPtr RecPtr);
 extern bool XLogBackgroundFlush(void);
 extern bool XLogNeedsFlush(XLogRecPtr RecPtr);
 extern int	XLogFileInit(XLogSegNo segno, TimeLineID tli);
-extern int	XLogFileOpen(XLogSegNo segno);
+extern int	XLogFileOpen(XLogSegNo segno, TimeLineID tli);
 
 extern void CheckXLogRemoved(XLogSegNo segno, TimeLineID tli);
 extern XLogSegNo XLogGetLastRemovedSegno(void);
-- 
2.24.3 (Apple Git-128)

0005-Change-ThisTimeLineID-to-be-static-rather-than-exter.patchapplication/octet-stream; name=0005-Change-ThisTimeLineID-to-be-static-rather-than-exter.patchDownload
From b467bc9b9eccd61f4c976abd243d0031a06012c0 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 29 Oct 2021 16:59:22 -0400
Subject: [PATCH 05/11] Change ThisTimeLineID to be 'static' rather than
 'extern'.

This means code outside xlog.c can no longer access it. Previous
commits have removed most cases whre that was needed, and this
commit cleans up the last few. In xlogfuncs.c, there are a couple
of places that use ThisTimeLineID to obtain the current timeline;
since the code can't run in recovery, use GetWALInsertionTimeLine()
instead. And, in xlogarchive.c, XLogArchiveNotifySeg() needs to
know the relevant timeline, so have the caller pass it as an
additional argument.
---
 src/backend/access/transam/xlog.c        | 4 ++--
 src/backend/access/transam/xlogarchive.c | 6 ++++--
 src/backend/access/transam/xlogfuncs.c   | 6 ++++--
 src/include/access/xlog.h                | 2 --
 src/include/access/xlogarchive.h         | 2 +-
 5 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 91ea1139d8..801175c177 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -192,7 +192,7 @@ CheckpointStatsData CheckpointStats;
  * ThisTimeLineID will be same in all backends --- it identifies current
  * WAL timeline for the database system.
  */
-TimeLineID	ThisTimeLineID = 0;
+static TimeLineID	ThisTimeLineID = 0;
 
 static XLogRecPtr LastRec;
 
@@ -2631,7 +2631,7 @@ XLogWrite(XLogwrtRqst WriteRqst, bool flexible)
 				LogwrtResult.Flush = LogwrtResult.Write;	/* end of page */
 
 				if (XLogArchivingActive())
-					XLogArchiveNotifySeg(openLogSegNo);
+					XLogArchiveNotifySeg(openLogSegNo, ThisTimeLineID);
 
 				XLogCtl->lastSegSwitchTime = (pg_time_t) time(NULL);
 				XLogCtl->lastSegSwitchLSN = LogwrtResult.Flush;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 26b023e754..7d56dad0de 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -498,11 +498,13 @@ XLogArchiveNotify(const char *xlog)
  * Convenience routine to notify using segment number representation of filename
  */
 void
-XLogArchiveNotifySeg(XLogSegNo segno)
+XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli)
 {
 	char		xlog[MAXFNAMELEN];
 
-	XLogFileName(xlog, ThisTimeLineID, segno, wal_segment_size);
+	Assert(tli != 0);
+
+	XLogFileName(xlog, tli, segno, wal_segment_size);
 	XLogArchiveNotify(xlog);
 }
 
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 5bebcc50dd..dd9a45c186 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -469,7 +469,8 @@ pg_walfile_name_offset(PG_FUNCTION_ARGS)
 	 * xlogfilename
 	 */
 	XLByteToPrevSeg(locationpoint, xlogsegno, wal_segment_size);
-	XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno, wal_segment_size);
+	XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
+				 wal_segment_size);
 
 	values[0] = CStringGetTextDatum(xlogfilename);
 	isnull[0] = false;
@@ -511,7 +512,8 @@ pg_walfile_name(PG_FUNCTION_ARGS)
 						 "pg_walfile_name()")));
 
 	XLByteToPrevSeg(locationpoint, xlogsegno, wal_segment_size);
-	XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno, wal_segment_size);
+	XLogFileName(xlogfilename, GetWALInsertionTimeLine(), xlogsegno,
+				 wal_segment_size);
 
 	PG_RETURN_TEXT_P(cstring_to_text(xlogfilename));
 }
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 22f1d37fb2..51a672b703 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -29,8 +29,6 @@
 #define SYNC_METHOD_OPEN_DSYNC	4	/* for O_DSYNC */
 extern int	sync_method;
 
-extern PGDLLIMPORT TimeLineID ThisTimeLineID;	/* current TLI */
-
 /*
  * Recovery target type.
  * Only set during a Point in Time recovery, not when in standby mode.
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 3edd1a976c..7dcf1bd2dd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -24,7 +24,7 @@ extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
 								   bool failOnSignal);
 extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
 extern void XLogArchiveNotify(const char *xlog);
-extern void XLogArchiveNotifySeg(XLogSegNo segno);
+extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
 extern void XLogArchiveForceDone(const char *xlog);
 extern bool XLogArchiveCheckDone(const char *xlog);
 extern bool XLogArchiveIsBusy(const char *xlog);
-- 
2.24.3 (Apple Git-128)

0009-xlog.c-Make-xlog_redo-not-depend-on-ThisTimeLineID-g.patchapplication/octet-stream; name=0009-xlog.c-Make-xlog_redo-not-depend-on-ThisTimeLineID-g.patchDownload
From 17d9c1e75fb233ba64b135696f005a85d04ca814 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 12:58:15 -0400
Subject: [PATCH 09/11] xlog.c: Make xlog_redo() not depend on ThisTimeLineID
 global variable.

The same value is available in shared memory as XLogCtl->replayEndTLI
so use that instead.
---
 src/backend/access/transam/xlog.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 054241403c..b6f30382eb 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -10349,6 +10349,10 @@ xlog_redo(XLogReaderState *record)
 {
 	uint8		info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
 	XLogRecPtr	lsn = record->EndRecPtr;
+	TimeLineID	replayTLI;
+
+	/* No other process can change this, so we can read it without a lock. */
+	replayTLI = XLogCtl->replayEndTLI;
 
 	/*
 	 * In XLOG rmgr, backup blocks are only used by XLOG_FPI and
@@ -10462,10 +10466,10 @@ xlog_redo(XLogReaderState *record)
 		 * We should've already switched to the new TLI before replaying this
 		 * record.
 		 */
-		if (checkPoint.ThisTimeLineID != ThisTimeLineID)
+		if (checkPoint.ThisTimeLineID != replayTLI)
 			ereport(PANIC,
 					(errmsg("unexpected timeline ID %u (should be %u) in checkpoint record",
-							checkPoint.ThisTimeLineID, ThisTimeLineID)));
+							checkPoint.ThisTimeLineID, replayTLI)));
 
 		RecoveryRestartPoint(&checkPoint);
 	}
@@ -10518,10 +10522,10 @@ xlog_redo(XLogReaderState *record)
 		SpinLockRelease(&XLogCtl->info_lck);
 
 		/* TLI should not change in an on-line checkpoint */
-		if (checkPoint.ThisTimeLineID != ThisTimeLineID)
+		if (checkPoint.ThisTimeLineID != replayTLI)
 			ereport(PANIC,
 					(errmsg("unexpected timeline ID %u (should be %u) in checkpoint record",
-							checkPoint.ThisTimeLineID, ThisTimeLineID)));
+							checkPoint.ThisTimeLineID, replayTLI)));
 
 		RecoveryRestartPoint(&checkPoint);
 	}
@@ -10548,10 +10552,10 @@ xlog_redo(XLogReaderState *record)
 		 * We should've already switched to the new TLI before replaying this
 		 * record.
 		 */
-		if (xlrec.ThisTimeLineID != ThisTimeLineID)
+		if (xlrec.ThisTimeLineID != replayTLI)
 			ereport(PANIC,
 					(errmsg("unexpected timeline ID %u (should be %u) in checkpoint record",
-							xlrec.ThisTimeLineID, ThisTimeLineID)));
+							xlrec.ThisTimeLineID, replayTLI)));
 	}
 	else if (info == XLOG_NOOP)
 	{
@@ -10621,7 +10625,7 @@ xlog_redo(XLogReaderState *record)
 			if (ControlFile->minRecoveryPoint < lsn)
 			{
 				ControlFile->minRecoveryPoint = lsn;
-				ControlFile->minRecoveryPointTLI = ThisTimeLineID;
+				ControlFile->minRecoveryPointTLI = replayTLI;
 			}
 			ControlFile->backupStartPoint = InvalidXLogRecPtr;
 			ControlFile->backupEndRequired = false;
@@ -10662,7 +10666,7 @@ xlog_redo(XLogReaderState *record)
 		if (minRecoveryPoint != InvalidXLogRecPtr && minRecoveryPoint < lsn)
 		{
 			ControlFile->minRecoveryPoint = lsn;
-			ControlFile->minRecoveryPointTLI = ThisTimeLineID;
+			ControlFile->minRecoveryPointTLI = replayTLI;
 		}
 
 		CommitTsParameterChange(xlrec.track_commit_timestamp,
-- 
2.24.3 (Apple Git-128)

0008-xlog.c-Use-XLogCtl-ThisTimeLineID-in-various-places.patchapplication/octet-stream; name=0008-xlog.c-Use-XLogCtl-ThisTimeLineID-in-various-places.patchDownload
From e5910978a754b3bfa9d2905584cc81a0cfb82cd8 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 11:42:55 -0400
Subject: [PATCH 08/11] xlog.c: Use XLogCtl->ThisTimeLineID in various places.

Where appropriate, instead of using ThisTimeLineID, use the value
from shared memory instead, to reduce dependencies on the global
variable. This is only safe after recovery is complete, because
prior to that, the global variable and the shared state don't
match.

This allows removal of some ugly logic in CreatRestartPoint() to
temporarily set ThisTimeLineID for purposes of tricking
RemoveOldXlogFiles and PreallocXlogFiles into doing the right thing,
and then clearing it again afterwards.
---
 src/backend/access/transam/xlog.c | 68 ++++++++++++++++++-------------
 1 file changed, 39 insertions(+), 29 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 95b64e6093..054241403c 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1035,6 +1035,7 @@ XLogInsertRecord(XLogRecData *rdata,
 	XLogRecPtr	StartPos;
 	XLogRecPtr	EndPos;
 	bool		prevDoPageWrites = doPageWrites;
+	TimeLineID	insertTLI;
 
 	/* we assume that all of the record header is in the first chunk */
 	Assert(rdata->len >= SizeOfXLogRecord);
@@ -1043,6 +1044,12 @@ XLogInsertRecord(XLogRecData *rdata,
 	if (!XLogInsertAllowed())
 		elog(ERROR, "cannot make new WAL entries during recovery");
 
+	/*
+	 * Given that we're not in recovery, ThisTimeLineID is set and can't
+	 * change, so we can read it without a lock.
+	 */
+	insertTLI = XLogCtl->ThisTimeLineID;
+
 	/*----------
 	 *
 	 * We have now done all the preparatory work we can without holding a
@@ -1146,7 +1153,7 @@ XLogInsertRecord(XLogRecData *rdata,
 		 * inserted. Copy the record in the space reserved.
 		 */
 		CopyXLogRecordToWAL(rechdr->xl_tot_len, isLogSwitch, rdata,
-							StartPos, EndPos, ThisTimeLineID);
+							StartPos, EndPos, insertTLI);
 
 		/*
 		 * Unless record is flagged as not important, update LSN of last
@@ -2901,6 +2908,7 @@ XLogFlush(XLogRecPtr record)
 {
 	XLogRecPtr	WriteRqstPtr;
 	XLogwrtRqst WriteRqst;
+	TimeLineID	insertTLI = XLogCtl->ThisTimeLineID;
 
 	/*
 	 * During REDO, we are reading not writing WAL.  Therefore, instead of
@@ -3021,7 +3029,7 @@ XLogFlush(XLogRecPtr record)
 		WriteRqst.Write = insertpos;
 		WriteRqst.Flush = insertpos;
 
-		XLogWrite(WriteRqst, ThisTimeLineID, false);
+		XLogWrite(WriteRqst, insertTLI, false);
 
 		LWLockRelease(WALWriteLock);
 		/* done */
@@ -3093,11 +3101,18 @@ XLogBackgroundFlush(void)
 	static TimestampTz lastflush;
 	TimestampTz now;
 	int			flushbytes;
+	TimeLineID	insertTLI;
 
 	/* XLOG doesn't need flushing during recovery */
 	if (RecoveryInProgress())
 		return false;
 
+	/*
+	 * Since we're not in recovery, ThisTimeLineID is set and can't change,
+	 * so we can read it without a lock.
+	 */
+	insertTLI = XLogCtl->ThisTimeLineID;
+
 	/* read LogwrtResult and update local state */
 	SpinLockAcquire(&XLogCtl->info_lck);
 	LogwrtResult = XLogCtl->LogwrtResult;
@@ -3188,7 +3203,7 @@ XLogBackgroundFlush(void)
 	if (WriteRqst.Write > LogwrtResult.Write ||
 		WriteRqst.Flush > LogwrtResult.Flush)
 	{
-		XLogWrite(WriteRqst, ThisTimeLineID, flexible);
+		XLogWrite(WriteRqst, insertTLI, flexible);
 	}
 	LWLockRelease(WALWriteLock);
 
@@ -3201,7 +3216,7 @@ XLogBackgroundFlush(void)
 	 * Great, done. To take some work off the critical path, try to initialize
 	 * as many of the no-longer-needed WAL buffers for future use as we can.
 	 */
-	AdvanceXLInsertBuffer(InvalidXLogRecPtr, ThisTimeLineID, true);
+	AdvanceXLInsertBuffer(InvalidXLogRecPtr, insertTLI, true);
 
 	/*
 	 * If we determined that we need to write data, but somebody else
@@ -9196,17 +9211,16 @@ CreateCheckPoint(int flags)
 	/*
 	 * An end-of-recovery checkpoint is created before anyone is allowed to
 	 * write WAL. To allow us to write the checkpoint record, temporarily
-	 * enable XLogInsertAllowed.  (This also ensures ThisTimeLineID is
-	 * initialized, which we need here and in AdvanceXLInsertBuffer.)
+	 * enable XLogInsertAllowed.
 	 */
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		oldXLogAllowed = LocalSetXLogInsertAllowed();
 
-	checkPoint.ThisTimeLineID = ThisTimeLineID;
+	checkPoint.ThisTimeLineID = XLogCtl->ThisTimeLineID;
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		checkPoint.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	else
-		checkPoint.PrevTimeLineID = ThisTimeLineID;
+		checkPoint.PrevTimeLineID = checkPoint.ThisTimeLineID;
 
 	checkPoint.fullPageWrites = Insert->fullPageWrites;
 
@@ -9463,14 +9477,15 @@ CreateCheckPoint(int flags)
 		KeepLogSeg(recptr, &_logSegNo);
 	}
 	_logSegNo--;
-	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr, ThisTimeLineID);
+	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, recptr,
+					   checkPoint.ThisTimeLineID);
 
 	/*
 	 * Make more log segments if needed.  (Do this after recycling old log
 	 * segments, since that may supply some of the needed files.)
 	 */
 	if (!shutdown)
-		PreallocXlogFiles(recptr, ThisTimeLineID);
+		PreallocXlogFiles(recptr, checkPoint.ThisTimeLineID);
 
 	/*
 	 * Truncate pg_subtrans if possible.  We can throw away all data before
@@ -9516,7 +9531,7 @@ CreateEndOfRecoveryRecord(void)
 	xlrec.end_time = GetCurrentTimestamp();
 
 	WALInsertLockAcquireExclusive();
-	xlrec.ThisTimeLineID = ThisTimeLineID;
+	xlrec.ThisTimeLineID = XLogCtl->ThisTimeLineID;
 	xlrec.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	WALInsertLockRelease();
 
@@ -9535,7 +9550,7 @@ CreateEndOfRecoveryRecord(void)
 	LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
 	ControlFile->time = (pg_time_t) time(NULL);
 	ControlFile->minRecoveryPoint = recptr;
-	ControlFile->minRecoveryPointTLI = ThisTimeLineID;
+	ControlFile->minRecoveryPointTLI = xlrec.ThisTimeLineID;
 	UpdateControlFile();
 	LWLockRelease(ControlFileLock);
 
@@ -9858,9 +9873,8 @@ CreateRestartPoint(int flags)
 	/*
 	 * Try to recycle segments on a useful timeline. If we've been promoted
 	 * since the beginning of this restartpoint, use the new timeline chosen
-	 * at end of recovery (RecoveryInProgress() sets ThisTimeLineID in that
-	 * case). If we're still in recovery, use the timeline we're currently
-	 * replaying.
+	 * at end of recovery.  If we're still in recovery, use the timeline we're
+	 * currently replaying.
 	 *
 	 * There is no guarantee that the WAL segments will be useful on the
 	 * current timeline; if recovery proceeds to a new timeline right after
@@ -9868,25 +9882,16 @@ CreateRestartPoint(int flags)
 	 * and will go wasted until recycled on the next restartpoint. We'll live
 	 * with that.
 	 */
-	if (RecoveryInProgress())
-		ThisTimeLineID = replayTLI;
+	if (!RecoveryInProgress())
+		replayTLI = XLogCtl->ThisTimeLineID;
 
-	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, ThisTimeLineID);
+	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
 
 	/*
 	 * Make more log segments if needed.  (Do this after recycling old log
 	 * segments, since that may supply some of the needed files.)
 	 */
-	PreallocXlogFiles(endptr, ThisTimeLineID);
-
-	/*
-	 * ThisTimeLineID is normally not set when we're still in recovery.
-	 * However, recycling/preallocating segments above needed ThisTimeLineID
-	 * to determine which timeline to install the segments on. Reset it now,
-	 * to restore the normal state of affairs for debugging purposes.
-	 */
-	if (RecoveryInProgress())
-		ThisTimeLineID = 0;
+	PreallocXlogFiles(endptr, replayTLI);
 
 	/*
 	 * Truncate pg_subtrans if possible.  We can throw away all data before
@@ -11792,7 +11797,12 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 		XLogBeginInsert();
 		XLogRegisterData((char *) (&startpoint), sizeof(startpoint));
 		stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
-		stoptli = ThisTimeLineID;
+
+		/*
+		 * Given that we're not in recovery, ThisTimeLineID is set and can't
+		 * change, so we can read it without a lock.
+		 */
+		stoptli = XLogCtl->ThisTimeLineID;
 
 		/*
 		 * Force a switch to a new xlog segment file, so that the backup is
-- 
2.24.3 (Apple Git-128)

0010-xlog.c-Adjust-some-more-functions-to-pass-the-TLI-ar.patchapplication/octet-stream; name=0010-xlog.c-Adjust-some-more-functions-to-pass-the-TLI-ar.patchDownload
From ce83825bc6ea756bf3cbd2e254ed737cd9bde427 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 14:25:21 -0400
Subject: [PATCH 10/11] xlog.c: Adjust some more functions to pass the TLI
 around.

Adjust ReadRecord() to take the replayTLI as an argument. Use
that value, rather than ThisTimeLineID, to set the minimum
recovery point TLI. Also pass the value through to ReadRecord()
via XLogPageReadPrivate, and from there pass it down to
WaitForWALToBecomeAvailable() and rescanLatestTimeLine(),
so that the latter doesn't need the global variable
ThisTimeLineID either.

To make all this work, a few other changes are needed. First,
pass a TLI down to ReadCheckpointRecord so that it can pass it
on to ReadRecord. Second, have read_backup_label() return the
timeline it obtains from the backup_label file and install
that temporarily as ThisTimeLineID. The reason for the latter
change is that, previously, ThisTimeLineID could be
uninitialized when ReadRecord() was called, as when we were
reading the checkpoint record. Though that seems to have no
consequences about which we need to be concerned, it's better
to have a legal value all the time.
---
 src/backend/access/transam/xlog.c | 75 +++++++++++++++++++------------
 1 file changed, 46 insertions(+), 29 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b6f30382eb..8a650b875e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -845,6 +845,7 @@ typedef struct XLogPageReadPrivate
 	int			emode;
 	bool		fetching_ckpt;	/* are we fetching a checkpoint record? */
 	bool		randAccess;
+	TimeLineID	replayTLI;
 } XLogPageReadPrivate;
 
 /*
@@ -931,7 +932,8 @@ static int	XLogFileReadAnyTLI(XLogSegNo segno, int emode, XLogSource source);
 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);
+										bool fetching_ckpt, XLogRecPtr tliRecPtr,
+										TimeLineID replayTLI);
 static void XLogShutdownWalRcv(void);
 static int	emode_for_corrupt_record(int emode, XLogRecPtr RecPtr);
 static void XLogFileClose(void);
@@ -946,12 +948,14 @@ static void ValidateXLOGDirectoryStructure(void);
 static void CleanupBackupHistory(void);
 static void UpdateMinRecoveryPoint(XLogRecPtr lsn, bool force);
 static XLogRecord *ReadRecord(XLogReaderState *xlogreader,
-							  int emode, bool fetching_ckpt);
+							  int emode, bool fetching_ckpt,
+							  TimeLineID replayTLI);
 static void CheckRecoveryConsistency(void);
 static bool PerformRecoveryXLogAction(void);
 static XLogRecord *ReadCheckpointRecord(XLogReaderState *xlogreader,
-										XLogRecPtr RecPtr, int whichChkpt, bool report);
-static bool rescanLatestTimeLine(void);
+										XLogRecPtr RecPtr, int whichChkpt, bool report,
+										TimeLineID replayTLI);
+static bool rescanLatestTimeLine(TimeLineID replayTLI);
 static void InitControlFile(uint64 sysidentifier);
 static void WriteControlFile(void);
 static void ReadControlFile(void);
@@ -967,6 +971,7 @@ static void xlog_outdesc(StringInfo buf, XLogReaderState *record);
 static void pg_start_backup_callback(int code, Datum arg);
 static void pg_stop_backup_callback(int code, Datum arg);
 static bool read_backup_label(XLogRecPtr *checkPointLoc,
+							  TimeLineID *backupLabelTLI,
 							  bool *backupEndRequired, bool *backupFromStandby);
 static bool read_tablespace_map(List **tablespaces);
 
@@ -4447,7 +4452,7 @@ CleanupBackupHistory(void)
  */
 static XLogRecord *
 ReadRecord(XLogReaderState *xlogreader, int emode,
-		   bool fetching_ckpt)
+		   bool fetching_ckpt, TimeLineID replayTLI)
 {
 	XLogRecord *record;
 	XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
@@ -4456,6 +4461,7 @@ ReadRecord(XLogReaderState *xlogreader, int emode,
 	private->fetching_ckpt = fetching_ckpt;
 	private->emode = emode;
 	private->randAccess = (xlogreader->ReadRecPtr == InvalidXLogRecPtr);
+	private->replayTLI = replayTLI;
 
 	/* This is the first attempt to read this page. */
 	lastSourceFailed = false;
@@ -4558,7 +4564,7 @@ ReadRecord(XLogReaderState *xlogreader, int emode,
 				if (ControlFile->minRecoveryPoint < EndRecPtr)
 				{
 					ControlFile->minRecoveryPoint = EndRecPtr;
-					ControlFile->minRecoveryPointTLI = ThisTimeLineID;
+					ControlFile->minRecoveryPointTLI = replayTLI;
 				}
 				/* update local copy */
 				minRecoveryPoint = ControlFile->minRecoveryPoint;
@@ -4612,7 +4618,7 @@ ReadRecord(XLogReaderState *xlogreader, int emode,
  * one and returns 'true'.
  */
 static bool
-rescanLatestTimeLine(void)
+rescanLatestTimeLine(TimeLineID replayTLI)
 {
 	List	   *newExpectedTLEs;
 	bool		found;
@@ -4654,7 +4660,7 @@ rescanLatestTimeLine(void)
 		ereport(LOG,
 				(errmsg("new timeline %u is not a child of database system timeline %u",
 						newtarget,
-						ThisTimeLineID)));
+						replayTLI)));
 		return false;
 	}
 
@@ -4668,7 +4674,7 @@ rescanLatestTimeLine(void)
 		ereport(LOG,
 				(errmsg("new timeline %u forked off current database system timeline %u before current recovery point %X/%X",
 						newtarget,
-						ThisTimeLineID,
+						replayTLI,
 						LSN_FORMAT_ARGS(EndRecPtr))));
 		return false;
 	}
@@ -6870,7 +6876,7 @@ StartupXLOG(void)
 	replay_image_masked = (char *) palloc(BLCKSZ);
 	primary_image_masked = (char *) palloc(BLCKSZ);
 
-	if (read_backup_label(&checkPointLoc, &backupEndRequired,
+	if (read_backup_label(&checkPointLoc, &ThisTimeLineID, &backupEndRequired,
 						  &backupFromStandby))
 	{
 		List	   *tablespaces = NIL;
@@ -6888,7 +6894,8 @@ StartupXLOG(void)
 		 * When a backup_label file is present, we want to roll forward from
 		 * the checkpoint it identifies, rather than using pg_control.
 		 */
-		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 0, true);
+		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 0, true,
+									  ThisTimeLineID);
 		if (record != NULL)
 		{
 			memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint));
@@ -6907,7 +6914,8 @@ StartupXLOG(void)
 			if (checkPoint.redo < checkPointLoc)
 			{
 				XLogBeginRead(xlogreader, checkPoint.redo);
-				if (!ReadRecord(xlogreader, LOG, false))
+				if (!ReadRecord(xlogreader, LOG, false,
+								checkPoint.ThisTimeLineID))
 					ereport(FATAL,
 							(errmsg("could not find redo location referenced by checkpoint record"),
 							 errhint("If you are restoring from a backup, touch \"%s/recovery.signal\" and add required recovery options.\n"
@@ -7023,7 +7031,9 @@ StartupXLOG(void)
 		/* Get the last valid checkpoint record. */
 		checkPointLoc = ControlFile->checkPoint;
 		RedoStartLSN = ControlFile->checkPointCopy.redo;
-		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true);
+		ThisTimeLineID = ControlFile->checkPointCopy.ThisTimeLineID;
+		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true,
+									  ThisTimeLineID);
 		if (record != NULL)
 		{
 			ereport(DEBUG1,
@@ -7523,12 +7533,12 @@ StartupXLOG(void)
 		{
 			/* back up to find the record */
 			XLogBeginRead(xlogreader, checkPoint.redo);
-			record = ReadRecord(xlogreader, PANIC, false);
+			record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
 		}
 		else
 		{
 			/* just have to read next record after CheckPoint */
-			record = ReadRecord(xlogreader, LOG, false);
+			record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
 		}
 
 		if (record != NULL)
@@ -7765,7 +7775,7 @@ StartupXLOG(void)
 				}
 
 				/* Else, try to fetch the next WAL record */
-				record = ReadRecord(xlogreader, LOG, false);
+				record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
 			} while (record != NULL);
 
 			/*
@@ -7889,7 +7899,7 @@ StartupXLOG(void)
 	 * what we consider the valid portion of WAL.
 	 */
 	XLogBeginRead(xlogreader, LastRec);
-	record = ReadRecord(xlogreader, PANIC, false);
+	record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
 	EndOfLog = EndRecPtr;
 
 	/*
@@ -8553,7 +8563,7 @@ LocalSetXLogInsertAllowed(void)
  */
 static XLogRecord *
 ReadCheckpointRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr,
-					 int whichChkpt, bool report)
+					 int whichChkpt, bool report, TimeLineID replayTLI)
 {
 	XLogRecord *record;
 	uint8		info;
@@ -8578,7 +8588,7 @@ ReadCheckpointRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr,
 	}
 
 	XLogBeginRead(xlogreader, RecPtr);
-	record = ReadRecord(xlogreader, LOG, true);
+	record = ReadRecord(xlogreader, LOG, true, replayTLI);
 
 	if (record == NULL)
 	{
@@ -12077,14 +12087,17 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
  * point, we will fail to restore a consistent database state.
  *
  * Returns true if a backup_label was found (and fills the checkpoint
- * location and its REDO location into *checkPointLoc and RedoStartLSN,
- * respectively); returns false if not. If this backup_label came from a
- * streamed backup, *backupEndRequired is set to true. If this backup_label
- * was created during recovery, *backupFromStandby is set to true.
+ * location and TLI into *checkPointLoc and *backupLabelTLI, respectively);
+ * returns false if not. If this backup_label came from a streamed backup,
+ * *backupEndRequired is set to true. If this backup_label was created during
+ * recovery, *backupFromStandby is set to true.
+ *
+ * Also sets the global variable RedoStartLSN with the LSN read from the
+ * backup file.
  */
 static bool
-read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired,
-				  bool *backupFromStandby)
+read_backup_label(XLogRecPtr *checkPointLoc, TimeLineID *backupLabelTLI,
+				  bool *backupEndRequired, bool *backupFromStandby)
 {
 	char		startxlogfilename[MAXFNAMELEN];
 	TimeLineID	tli_from_walseg,
@@ -12193,6 +12206,8 @@ read_backup_label(XLogRecPtr *checkPointLoc, bool *backupEndRequired,
 				 errmsg("could not read file \"%s\": %m",
 						BACKUP_LABEL_FILE)));
 
+	*backupLabelTLI = tli_from_walseg;
+
 	return true;
 }
 
@@ -12469,7 +12484,8 @@ retry:
 		if (!WaitForWALToBecomeAvailable(targetPagePtr + reqLen,
 										 private->randAccess,
 										 private->fetching_ckpt,
-										 targetRecPtr))
+										 targetRecPtr,
+										 private->replayTLI))
 		{
 			if (readFile >= 0)
 				close(readFile);
@@ -12633,7 +12649,8 @@ next_record_is_invalid:
  */
 static bool
 WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
-							bool fetching_ckpt, XLogRecPtr tliRecPtr)
+							bool fetching_ckpt, XLogRecPtr tliRecPtr,
+							TimeLineID replayTLI)
 {
 	static TimestampTz last_fail_time = 0;
 	TimestampTz now;
@@ -12756,7 +12773,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 */
 					if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
 					{
-						if (rescanLatestTimeLine())
+						if (rescanLatestTimeLine(replayTLI))
 						{
 							currentSource = XLOG_FROM_ARCHIVE;
 							break;
@@ -12883,7 +12900,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 						 */
 						if (recoveryTargetTimeLineGoal ==
 							RECOVERY_TARGET_TIMELINE_LATEST)
-							rescanLatestTimeLine();
+							rescanLatestTimeLine(replayTLI);
 
 						startWalReceiver = true;
 					}
-- 
2.24.3 (Apple Git-128)

0007-xlog.c-Invent-openLogTLI.patchapplication/octet-stream; name=0007-xlog.c-Invent-openLogTLI.patchDownload
From f905cfd9d2ee9962038ed00435eef47b0ced144c Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 10:56:18 -0400
Subject: [PATCH 07/11] xlog.c: Invent openLogTLI.

Currently, XLogFileClose() depends on the TLI corresponding to
openLogSegNo being ThisTimeLineID. Instead, invent a new variable
openLogTLI which is precisely the timeline ID of the xlog file
we're accessing via openLogFile.
---
 src/backend/access/transam/xlog.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 16f90769c2..95b64e6093 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -801,12 +801,15 @@ static const char *const xlogSourceNames[] = {"any", "archive", "pg_wal", "strea
 
 /*
  * openLogFile is -1 or a kernel FD for an open log file segment.
- * openLogSegNo identifies the segment.  These variables are only used to
- * write the XLOG, and so will normally refer to the active segment.
+ * openLogSegNo identifies the segment, and openLogTLI the corresponding TLI.
+ * These variables are only used to write the XLOG, and so will normally refer
+ * to the active segment.
+ *
  * Note: call Reserve/ReleaseExternalFD to track consumption of this FD.
  */
 static int	openLogFile = -1;
 static XLogSegNo openLogSegNo = 0;
+static TimeLineID openLogTLI = 0;
 
 /*
  * These variables are used similarly to the ones above, but for reading
@@ -2511,6 +2514,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
 				XLogFileClose();
 			XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,
 							wal_segment_size);
+			openLogTLI = tli;
 
 			/* create/use new log file */
 			openLogFile = XLogFileInit(openLogSegNo, tli);
@@ -2522,6 +2526,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
 		{
 			XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,
 							wal_segment_size);
+			openLogTLI = tli;
 			openLogFile = XLogFileOpen(openLogSegNo, tli);
 			ReserveExternalFD();
 		}
@@ -2695,6 +2700,7 @@ XLogWrite(XLogwrtRqst WriteRqst, TimeLineID tli, bool flexible)
 			{
 				XLByteToPrevSeg(LogwrtResult.Write, openLogSegNo,
 								wal_segment_size);
+				openLogTLI = tli;
 				openLogFile = XLogFileOpen(openLogSegNo, tli);
 				ReserveExternalFD();
 			}
@@ -3946,7 +3952,7 @@ XLogFileClose(void)
 		char		xlogfname[MAXFNAMELEN];
 		int			save_errno = errno;
 
-		XLogFileName(xlogfname, ThisTimeLineID, openLogSegNo, wal_segment_size);
+		XLogFileName(xlogfname, openLogTLI, openLogSegNo, wal_segment_size);
 		errno = save_errno;
 		ereport(PANIC,
 				(errcode_for_file_access(),
@@ -5416,6 +5422,7 @@ BootStrapXLOG(void)
 	record->xl_crc = crc;
 
 	/* Create first XLOG segment file */
+	openLogTLI = ThisTimeLineID;
 	openLogFile = XLogFileInit(1, ThisTimeLineID);
 
 	/*
@@ -10863,7 +10870,7 @@ assign_xlog_sync_method(int new_sync_method, void *extra)
 				int			save_errno;
 
 				save_errno = errno;
-				XLogFileName(xlogfname, ThisTimeLineID, openLogSegNo,
+				XLogFileName(xlogfname, openLogTLI, openLogSegNo,
 							 wal_segment_size);
 				errno = save_errno;
 				ereport(PANIC,
-- 
2.24.3 (Apple Git-128)

0011-Demote-ThisTimeLineID-to-a-local-variable.patchapplication/octet-stream; name=0011-Demote-ThisTimeLineID-to-a-local-variable.patchDownload
From b71a67d21df2028396a533097d74a828ed421f3f Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 1 Nov 2021 14:39:36 -0400
Subject: [PATCH 11/11] Demote ThisTimeLineID to a local variable.

Instead of a file-level global variable, it can now be made local
to StartupXLOG. The only other code that still accesses it is in
BootstrapXLOG, where we now just use a constant instead.
---
 src/backend/access/transam/xlog.c | 35 ++++++++++---------------------
 1 file changed, 11 insertions(+), 24 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8a650b875e..dcc215dcc2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -88,6 +88,9 @@ extern uint32 bootstrap_data_checksum_version;
 #define RECOVERY_COMMAND_FILE	"recovery.conf"
 #define RECOVERY_COMMAND_DONE	"recovery.done"
 
+/* timeline ID to be used when bootstrapping */
+#define BootstrapTimeLineID		1
+
 /* User-settable parameters */
 int			max_wal_size_mb = 1024; /* 1 GB */
 int			min_wal_size_mb = 80;	/* 80 MB */
@@ -188,12 +191,6 @@ const struct config_enum_entry recovery_target_action_options[] = {
  */
 CheckpointStatsData CheckpointStats;
 
-/*
- * ThisTimeLineID will be same in all backends --- it identifies current
- * WAL timeline for the database system.
- */
-static TimeLineID	ThisTimeLineID = 0;
-
 static XLogRecPtr LastRec;
 
 /* Local copy of WalRcv->flushedUpto */
@@ -5368,9 +5365,6 @@ BootStrapXLOG(void)
 	sysidentifier |= ((uint64) tv.tv_usec) << 12;
 	sysidentifier |= getpid() & 0xFFF;
 
-	/* First timeline ID is always 1 */
-	ThisTimeLineID = 1;
-
 	/* page buffer must be aligned suitably for O_DIRECT */
 	buffer = (char *) palloc(XLOG_BLCKSZ + XLOG_BLCKSZ);
 	page = (XLogPageHeader) TYPEALIGN(XLOG_BLCKSZ, buffer);
@@ -5384,8 +5378,8 @@ BootStrapXLOG(void)
 	 * used, so that we can use 0/0 to mean "before any valid WAL segment".
 	 */
 	checkPoint.redo = wal_segment_size + SizeOfXLogLongPHD;
-	checkPoint.ThisTimeLineID = ThisTimeLineID;
-	checkPoint.PrevTimeLineID = ThisTimeLineID;
+	checkPoint.ThisTimeLineID = BootstrapTimeLineID;
+	checkPoint.PrevTimeLineID = BootstrapTimeLineID;
 	checkPoint.fullPageWrites = fullPageWrites;
 	checkPoint.nextXid =
 		FullTransactionIdFromEpochAndXid(0, FirstNormalTransactionId);
@@ -5413,7 +5407,7 @@ BootStrapXLOG(void)
 	/* Set up the XLOG page header */
 	page->xlp_magic = XLOG_PAGE_MAGIC;
 	page->xlp_info = XLP_LONG_HEADER;
-	page->xlp_tli = ThisTimeLineID;
+	page->xlp_tli = BootstrapTimeLineID;
 	page->xlp_pageaddr = wal_segment_size;
 	longpage = (XLogLongPageHeader) page;
 	longpage->xlp_sysid = sysidentifier;
@@ -5443,8 +5437,8 @@ BootStrapXLOG(void)
 	record->xl_crc = crc;
 
 	/* Create first XLOG segment file */
-	openLogTLI = ThisTimeLineID;
-	openLogFile = XLogFileInit(1, ThisTimeLineID);
+	openLogTLI = BootstrapTimeLineID;
+	openLogFile = XLogFileInit(1, BootstrapTimeLineID);
 
 	/*
 	 * We needn't bother with Reserve/ReleaseExternalFD here, since we'll
@@ -6681,7 +6675,8 @@ StartupXLOG(void)
 				checkPointLoc,
 				EndOfLog;
 	TimeLineID	EndOfLogTLI;
-	TimeLineID	PrevTimeLineID;
+	TimeLineID	ThisTimeLineID,
+				PrevTimeLineID;
 	XLogRecord *record;
 	TransactionId oldestActiveXID;
 	bool		backupEndRequired = false;
@@ -8661,21 +8656,13 @@ ReadCheckpointRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr,
 /*
  * This must be called in a backend process before creating WAL records
  * (except in a standalone backend, which does StartupXLOG instead).  We need
- * to initialize the local copies of ThisTimeLineID and RedoRecPtr.
- *
- * Note: before Postgres 8.0, we went to some effort to keep the postmaster
- * process's copies of ThisTimeLineID and RedoRecPtr valid too.  This was
- * unnecessary however, since the postmaster itself never touches XLOG anyway.
+ * to initialize the local copy of RedoRecPtr.
  */
 void
 InitXLOGAccess(void)
 {
 	XLogCtlInsert *Insert = &XLogCtl->Insert;
 
-	/* ThisTimeLineID doesn't change so we need no lock to copy it */
-	ThisTimeLineID = XLogCtl->ThisTimeLineID;
-	Assert(ThisTimeLineID != 0 || IsBootstrapProcessingMode());
-
 	/* set wal_segment_size */
 	wal_segment_size = ControlFile->xlog_seg_size;
 
-- 
2.24.3 (Apple Git-128)

#2Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#1)
Re: removing global variable ThisTimeLineID

On Mon, Nov 01, 2021 at 03:16:27PM -0400, Robert Haas wrote:

At the risk of stating the obvious, using the same variable for
different purposes at different times is not a great programming
practice. Commits 2f5c4397c39dea49c5608ba583868e26d767fc32 and
902a2c280012557b85c7e0fce3f6f0e355cb2d69 show that the possibility of
programmer error is not zero, even though neither of those issues are
serious.

This can lead to more serious issues, see 595b9cba as recent example.
I agree that getting rid of it in the long term is appealing.

Moreover, the global variable itself seems to do nothing but
invite programming errors. The name itself is a disaster. What is
"this" timeline ID? Well, uh, the right one, of course! We should be
more clear about what we mean: the timeline into which we are
inserting, the timeline from which we are replaying, the timeline from
which we are performing logical decoding. I suspect that having a
global variable here isn't even really saving us anything much, as a
value that does not change can be read from shared memory without a
lock.

Some callbacks related to the WAL reader to read a patch have
introduced a dependency to ThisTimelineID as a mean to be used for
logical decoding on standbys. This is discussed a bit on this thread,
for example:
/messages/by-id/ddc31aa9-8083-58b7-0b47-e371cd4c705b@oss.nttdata.com

Tracking properly the TLI in logical decoding was mentioned by Andres
multiple times on -hackers, and this has not been really done. Using
ThisTimelineID for this purpose is something I've found confusing
myself, FWIW, so this is a step in the good direction.

I would like to clean this up. Attached is a series of patches which
try to do that. For ease of review, this is separated out into quite a
few separate patches, but most likely we'd combine some of them for
commit. Patches 0001 through 0004 eliminate all use of the global
variable ThisTimeLineID outside of xlog.c, and patch 0005 makes it
"static" so that it ceases to be visible outside of xlog.c. Patches
0006 to 0010 clean up uses of ThisTimeLineID itself, passing it around
as a function parameter, or otherwise arranging to fetch the relevant
timeline ID from someplace sensible rather than using the global
variable as a scratchpad. Finally, patch 0011 deletes the global
variable.

I have not made a serious attempt to clear up all of the
terminological confusion created by the term ThisTimeLineID, which
also appears as part of some structs, so you'll still see that name in
the source code after applying these patches, just not as the name of
a global variable. I have, however, used names like insertTLI and
replayTLI in many places changed by the patch, and I think that makes
it significantly more clear which timeline is being discussed in each
case. In some places I have endeavored to improve the comments. There
is doubtless more that could be done here, but I think this is a
fairly respectable start.

Thanks for splitting things this way. This helps a lot.

0001 looks fair.

+   /*
+    * If we're writing and flushing WAL, the time line can't be changing,
+    * so no lock is required.
+    */
+   if (insertTLI)
+       *insertTLI = XLogCtl->ThisTimeLineID;
In 0002, there is no downside in putting that in the spinlock section
either, no?  It seems to me that we should be able to call this one
even in recovery, as flush LSNs exist while in recovery.
+TimeLineID
+GetWALInsertionTimeLine(void)
+{
+   Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
Okay, that's cheaper.

The series 0003-0006 seemed rather fine, at quick glance.

In 0007, XLogFileClose() should reset openLogTLI. The same can be
said in BootStrapXLOG() before creating the control file. It may be
cleaner here to introduce an InvalidTimelineId, as discussed a couple
of days ago.

Some comments at the top of recoveryTargetTLIRequested need a refresh
with their references to ThisTimelineID.

The handling of XLogCtl->ThisTimeLineID still seems a bit blurry when
it comes to timeline switches because we don't update it while in
recovery when replaying a end-of-recovery record. This could at least
be documented better.
--
Michael

#3Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#2)
Re: removing global variable ThisTimeLineID

On Mon, Nov 1, 2021 at 11:33 PM Michael Paquier <michael@paquier.xyz> wrote:

+   /*
+    * If we're writing and flushing WAL, the time line can't be changing,
+    * so no lock is required.
+    */
+   if (insertTLI)
+       *insertTLI = XLogCtl->ThisTimeLineID;
In 0002, there is no downside in putting that in the spinlock section
either, no?  It seems to me that we should be able to call this one
even in recovery, as flush LSNs exist while in recovery.

I don't think it matters very much from a performance point of view,
although putting a branch inside of a spinlock-protected section may
be undesirable. My bigger issues with this kind of idea is that we
don't want to use spinlocks as a "security blanket" (as in Linus from
Peanuts). Every time we use a spinlock, or any other kind of locking
mechanism, we should have a clear idea what we're protecting ourselves
against. I'm quite suspicious that there are a number of places where
we're taking spinlocks in xlog.c just to feel virtuous, and not
because it does anything useful, and I don't particularly want to
increase the number of such places.

Also, XLogCtl->ThisTimeLineID isn't set until the end of recovery, so
calling this function during recovery would be a mistake. There seem
to be a number of interface functions in xlog.c that should only be
called when not in recovery, and neither their names nor their
comments make that clear. I wasn't bold enough to go label all the
ones I think fall into that category, but maybe we should. Honestly,
the naming of things in this file is annoyingly bad in general. My
favorite example, found during this project, is:

#define ConvertToXSegs(x, segsize) XLogMBVarToSegs((x), (segsize))

It's not good enough to have one name that's difficult to understand
or remember or capitalize properly -- let's have TWO -- for the same
thing!

In 0007, XLogFileClose() should reset openLogTLI. The same can be
said in BootStrapXLOG() before creating the control file. It may be
cleaner here to introduce an InvalidTimelineId, as discussed a couple
of days ago.

I thought about that, but I think it's pointless. I think we can just
say that if openLogFile == -1, openLogSegNo and openLogTLI are
meaningless. I don't think we're currently resetting openLogSegNo to
an invalid value either, so I don't think openLogTLI should be treated
differently. I'm not opposed to introducing InvalidTimeLineID on
principle, and I looked for a way of doing it in this patch set, but
it didn't seem all that valuable, and I feel that someone who cares
about it can do it as a separate effort after I commit these.
Honestly, I think these patches greatly reduce the need to be afraid
of leaving the TLI unset, because they remove the crutch of hoping
that ThisTimeLineID is initialized to the proper value. You actually
have to think about which TLI you are going to pass. Now I'm very sure
further improvement is possible, but I think this patch set is complex
enough already, and I'd like to get it committed before contemplating
any other work in this area.

Some comments at the top of recoveryTargetTLIRequested need a refresh
with their references to ThisTimelineID.

After thinking this over, I think we should just delete the entire
first paragraph, so that the comments start like this:

/*
* recoveryTargetTimeLineGoal: what the user requested, if any
*
* recoveryTargetTLIRequested: numeric value of requested timeline, if constant

I don't see that we'd be losing anything that way. When you look at
that paragraph now, the first sentence is just confusing the issue,
because we actually don't care about XLogCtl->ThisTimeLineID during
recovery, because it's not set. We do care about plain old
ThisTimeLineID, but that's going to be demoted to a local variable by
these patches, and there just doesn't seem to be any reason to discuss
it here. The second sentence is wrong already, because the rmgr
routines do not rely on ThisTimeLineID. And the third sentence is also
wrong and/or confusing, because not all of the variables that follow
are TLIs -- only 3 of 5 are. I don't actually think there's any useful
introduction we need to provide here; we just need to tell people what
the variables we're about to declare do.

The handling of XLogCtl->ThisTimeLineID still seems a bit blurry when
it comes to timeline switches because we don't update it while in
recovery when replaying a end-of-recovery record. This could at least
be documented better.

We don't update it during recovery at all. There's exactly one
assignment statement for that variable and it's here:

/* Save the selected TimeLineID in shared memory, too */
XLogCtl->ThisTimeLineID = ThisTimeLineID;
XLogCtl->PrevTimeLineID = PrevTimeLineID;

That's after the main redo loop completes.

--
Robert Haas
EDB: http://www.enterprisedb.com

#4Amul Sul
sulamul@gmail.com
In reply to: Robert Haas (#1)
Re: removing global variable ThisTimeLineID

On Tue, Nov 2, 2021 at 12:46 AM Robert Haas <robertmhaas@gmail.com> wrote:

[...]
I would like to clean this up. Attached is a series of patches which
try to do that. For ease of review, this is separated out into quite a
few separate patches, but most likely we'd combine some of them for
commit. Patches 0001 through 0004 eliminate all use of the global
variable ThisTimeLineID outside of xlog.c, and patch 0005 makes it
"static" so that it ceases to be visible outside of xlog.c. Patches
0006 to 0010 clean up uses of ThisTimeLineID itself, passing it around
as a function parameter, or otherwise arranging to fetch the relevant
timeline ID from someplace sensible rather than using the global
variable as a scratchpad. Finally, patch 0011 deletes the global
variable.

I have not made a serious attempt to clear up all of the
terminological confusion created by the term ThisTimeLineID, which
also appears as part of some structs, so you'll still see that name in
the source code after applying these patches, just not as the name of
a global variable. I have, however, used names like insertTLI and
replayTLI in many places changed by the patch, and I think that makes
it significantly more clear which timeline is being discussed in each
case. In some places I have endeavored to improve the comments. There
is doubtless more that could be done here, but I think this is a
fairly respectable start.

Opinions welcome.

I had a plan to look at these patches closely, but unfortunately,
didn't get enough time; and might not be able to spend time in the
rest of this week. Here are a few thoughts that I had in the initial
go-through, but that may or may not sound very interesting:

0002:
-GetFlushRecPtr(void)
+GetFlushRecPtr(TimeLineID *insertTLI)

Should we rename this argument to more generic as "tli", like
GetStandbyFlushRecPtr, since the caller in 003 patch trying to fetch a
TLI that means different for them, e.g. currTLI, FlushTLI, etc.

0004:
 static int
-XLogFileInitInternal(XLogSegNo logsegno, bool *added, char *path)
+XLogFileInitInternal(XLogSegNo logsegno, TimeLineID logtli,
+                    bool *added, char *path)
 int
-XLogFileInit(XLogSegNo logsegno)
+XLogFileInit(XLogSegNo logsegno, TimeLineID logtli)
 {

How about logsegtli instead, to be inlined with logsegno ?

0007:
static XLogSegNo openLogSegNo = 0;
+static TimeLineID openLogTLI = 0;

Similarly, openLogSegTLI ?

0008:
+   /*
+    * Given that we're not in recovery, ThisTimeLineID is set and can't
+    * change, so we can read it without a lock.
+    */
+   insertTLI = XLogCtl->ThisTimeLineID;

Can't GetWALInsertionTimeLine() called instead? I guess the reason is
to avoid the unnecessary overhead involved in the function call. (Same
at a few other places.)

Regards,
Amul

#5Robert Haas
robertmhaas@gmail.com
In reply to: Amul Sul (#4)
Re: removing global variable ThisTimeLineID

On Wed, Nov 3, 2021 at 9:12 AM Amul Sul <sulamul@gmail.com> wrote:

0002:
-GetFlushRecPtr(void)
+GetFlushRecPtr(TimeLineID *insertTLI)

Should we rename this argument to more generic as "tli", like
GetStandbyFlushRecPtr, since the caller in 003 patch trying to fetch a
TLI that means different for them, e.g. currTLI, FlushTLI, etc.

It's possible that more consistency would be better here, but I don't
think making this name more generic is going in the right direction.
If somebody gets the TLI using this function, it's the timeline into
which a system not in recovery is inserting WAL, which is the same
timeline to which WAL is being flushed. I think it's important for
this name to try to make that clear. It doesn't really matter if
someone treats the insertTLI as the writeTLI or the flushTLI since on
a system in production they're all the same - but they must not
confuse it with, say, the replayTLI.

How about logsegtli instead, to be inlined with logsegno ?
Similarly, openLogSegTLI ?

Hmm, well I guess it depends on how you think the words group. If you
logsegno means "the number of the log segment" then it would make
sense to have logsegli to mean "the tli of the log segment." But I
think of logsegno as meaning "the segment number of the log file," so
it makes more sense to have logtli to mean "the TLI of the log file".
I think it's definitely not a "log segment file" - just a "log file".

Can't GetWALInsertionTimeLine() called instead? I guess the reason is
to avoid the unnecessary overhead involved in the function call. (Same
at a few other places.)

That, plus to avoid failing the assertion in that function. As we've
discussed on the ALTER SYSTEM READ ONLY thread, xlog.c itself inserts
some WAL before recovery is officially over.

--
Robert Haas
EDB: http://www.enterprisedb.com

#6Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Robert Haas (#1)
Re: removing global variable ThisTimeLineID

I looked at these in a very quick skim. I agree that this is a
good step in a good direction.

I 'git rebase -x'd this series in order to compile and run the tests on
each patch individually. There are no compiler warnings anywhere and no
test failures.

--
Álvaro Herrera Valdivia, Chile — https://www.EnterpriseDB.com/
"La victoria es para quien se atreve a estar solo"

#7Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#6)
Re: removing global variable ThisTimeLineID

On Fri, Nov 5, 2021 at 9:49 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

I looked at these in a very quick skim. I agree that this is a
good step in a good direction.

Cool. I have now committed these. I grouped them into just 3 commits
rather than having 11 separate commits as I did in my earlier posting,
but the content is the same.

I 'git rebase -x'd this series in order to compile and run the tests on
each patch individually. There are no compiler warnings anywhere and no
test failures.

Thanks for checking. It's hard to tell whether the stuff that works on
my machine will work everywhere, unless someone tests it.

--
Robert Haas
EDB: http://www.enterprisedb.com

#8Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#3)
Re: removing global variable ThisTimeLineID

On Tue, Nov 02, 2021 at 08:45:57AM -0400, Robert Haas wrote:

Also, XLogCtl->ThisTimeLineID isn't set until the end of recovery, so
calling this function during recovery would be a mistake. There seem
to be a number of interface functions in xlog.c that should only be
called when not in recovery, and neither their names nor their
comments make that clear. I wasn't bold enough to go label all the
ones I think fall into that category, but maybe we should.

I got to wonder whether it would be better to add in GetFlushRecPtr()
the same assertion as GetWALInsertionTimeLine(). All the in-core
callers of this routine already assume that, and it would be buggy if
one passes down insertTLI to get the current TLI.

I thought about that, but I think it's pointless. I think we can just
say that if openLogFile == -1, openLogSegNo and openLogTLI are
meaningless. I don't think we're currently resetting openLogSegNo to
an invalid value either, so I don't think openLogTLI should be treated
differently.

Wouldn't it be better to at least document that as a comment rather
than letting the reader guess it, then? I agree that this is a minor
point, though.

I'm not opposed to introducing InvalidTimeLineID on
principle, and I looked for a way of doing it in this patch set, but
it didn't seem all that valuable, and I feel that someone who cares
about it can do it as a separate effort after I commit these.

No issues from me. I have bumped into a case just at the end of last
week where I wanted a better way to tell if a TLI is invalid rather
than leaving things to 0, FWIW.

The handling of XLogCtl->ThisTimeLineID still seems a bit blurry when
it comes to timeline switches because we don't update it while in
recovery when replaying a end-of-recovery record. This could at least
be documented better.

We don't update it during recovery at all. There's exactly one
assignment statement for that variable and it's here:

Indeed. It looks like my reading was sloppy here.
--
Michael

#9Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#8)
Re: removing global variable ThisTimeLineID

On Sun, Nov 7, 2021 at 11:24 PM Michael Paquier <michael@paquier.xyz> wrote:

I got to wonder whether it would be better to add in GetFlushRecPtr()
the same assertion as GetWALInsertionTimeLine(). All the in-core
callers of this routine already assume that, and it would be buggy if
one passes down insertTLI to get the current TLI.

Perhaps. That's related to the point I made before, that it might be a
good idea to be more clear about which of these functions are intended
to be used in recovery and which ones are intended to be used in
normal running. I don't rule out the possibility of doing some more
research here and tightening that up, but I don't want to go start
tweaking things unless I'm sure I know what I'm talking about. This
stuff is so confusing that I don't want to take any risk of making
changes that turn out to be incorrect.

I thought about that, but I think it's pointless. I think we can just
say that if openLogFile == -1, openLogSegNo and openLogTLI are
meaningless. I don't think we're currently resetting openLogSegNo to
an invalid value either, so I don't think openLogTLI should be treated
differently.

Wouldn't it be better to at least document that as a comment rather
than letting the reader guess it, then? I agree that this is a minor
point, though.

I don't think it's very confusing, but if you want to improve the
comments, go for it!

The handling of XLogCtl->ThisTimeLineID still seems a bit blurry when
it comes to timeline switches because we don't update it while in
recovery when replaying a end-of-recovery record. This could at least
be documented better.

We don't update it during recovery at all. There's exactly one
assignment statement for that variable and it's here:

Indeed. It looks like my reading was sloppy here.

I can hardly blame you. There are comments all over the place claiming
that this thing and that thing are the same when they're only
sometimes the same. Or, as in this case, things that are named the
same when they're actually different. I'm actually tempted to make a
pass over the comments related to this topic and just try to make
things more comprehensible. It's super-confusing as things stand.

--
Robert Haas
EDB: http://www.enterprisedb.com

#10Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#9)
1 attachment(s)
Re: removing global variable ThisTimeLineID

On Mon, Nov 8, 2021 at 8:27 AM Robert Haas <robertmhaas@gmail.com> wrote:

Perhaps. That's related to the point I made before, that it might be a
good idea to be more clear about which of these functions are intended
to be used in recovery and which ones are intended to be used in
normal running. I don't rule out the possibility of doing some more
research here and tightening that up, but I don't want to go start
tweaking things unless I'm sure I know what I'm talking about. This
stuff is so confusing that I don't want to take any risk of making
changes that turn out to be incorrect.

Well I went through and it seems to be OK: all the existing callers of
that function first verify that we're not in recovery. The patch to
make logical decoding work in standby mode might change that, but as
of now it's so. So in the attached patch, I have added the assertion
and comments that you have proposed there. I also removed the
misleading comment we discussed before. In addition to that, I renamed
the local variable ThisTimeLineID to replayTLI, and I also renamed
XLogCtl->ThisTimeLineID to InsertTimeLineID, which I think should make
things clearer: imagine if things that were used for different
purposes had different names!

Even with this patch, the name ThisTimeLineID is still used for
multiple purposes. It remains part of the CheckPoint struct, and also
part of the xl_end_of_recovery struct. But in both of those cases, the
name ThisTimeLineID actually makes sense, because what we're thinking
about is whether there's a timeline change, so we have ThisTimeLineID
and PrevTimeLineID and it seems fairly clear what that's supposed to
mean.

Thoughts?

--
Robert Haas
EDB: http://www.enterprisedb.com

Attachments:

0001-More-cleanup-of-ThisTimeLineID.patchapplication/octet-stream; name=0001-More-cleanup-of-ThisTimeLineID.patchDownload
From b7fdef657a866d3064296022212a55c911ae85bf Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 8 Nov 2021 12:34:29 -0500
Subject: [PATCH] More cleanup of 'ThisTimeLineID'.

In XLogCtlData, rename the structure member ThisTimeLineID to
InsertTimeLineID and update the comments to make clear that it's only
expected to be set after recovery is complete.

In StartupXLOG, replace the local variables ThisTimeLineID and
PrevTimeLineID with new local variables replayTLI and newTLI.  In the
old scheme, ThisTimeLineID was the replay TLI until we created a new
timeline, and after that the replay TLI was in PrevTimeLineID. Now,
replayTLI is the TLI from which we last replayed WAL throughout the
entire function, and newTLI is either that, or the new timeline created
upon promotion.

Remove some misleading comments from the comment block just above where
recoveryTargetTimeLineGoal and friends are declared. It's become
incorrect, not only because ThisTimeLineID as a variable is now gone,
but also because the rmgr code does not care about ThisTimeLineID and
has not since what used to be the TLI field in the page header was
repurposed to store the page checksum.

Add a comment GetFlushRecPtr that it's only supposed to be used in
normal running, and an assertion to verify that this is so.

Per some ideas from Michael Paquier and some of my own.
---
 src/backend/access/transam/xlog.c | 115 +++++++++++++++---------------
 1 file changed, 57 insertions(+), 58 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5cda30836f..8263b720a2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -301,13 +301,6 @@ static char recoveryStopName[MAXFNAMELEN];
 static bool recoveryStopAfter;
 
 /*
- * During normal operation, the only timeline we care about is ThisTimeLineID.
- * During recovery, however, things are more complicated.  To simplify life
- * for rmgr code, we keep ThisTimeLineID set to the "current" timeline as we
- * scan through the WAL history (that is, it is the line that was active when
- * the currently-scanned WAL record was generated).  We also need these
- * timeline values:
- *
  * recoveryTargetTimeLineGoal: what the user requested, if any
  *
  * recoveryTargetTLIRequested: numeric value of requested timeline, if constant
@@ -321,8 +314,9 @@ static bool recoveryStopAfter;
  * candidate WAL files to open at all.
  *
  * curFileTLI: the TLI appearing in the name of the current input WAL file.
- * (This is not necessarily the same as ThisTimeLineID, because we could
- * be scanning data that was copied from an ancestor timeline when the current
+ * (This is not necessarily the same as the timeline from which we are
+ * replaying WAL, which StartupXLOG calls replayTLI, because we could be
+ * scanning data that was copied from an ancestor timeline when the current
  * file was created.)  During a sequential scan we do not allow this value
  * to decrease.
  */
@@ -630,12 +624,14 @@ typedef struct XLogCtlData
 	int			XLogCacheBlck;	/* highest allocated xlog buffer index */
 
 	/*
-	 * Shared copy of ThisTimeLineID. Does not change after end-of-recovery.
-	 * If we created a new timeline when the system was started up,
+	 * InsertTimeLineID is the timeline into which new WAL is being inserted
+	 * and flushed. It is zero during recovery, and does not change once set.
+	 *
+	 * If we create a new timeline when the system was started up,
 	 * PrevTimeLineID is the old timeline's ID that we forked off from.
-	 * Otherwise it's equal to ThisTimeLineID.
+	 * Otherwise it's equal to InsertTimeLineID.
 	 */
-	TimeLineID	ThisTimeLineID;
+	TimeLineID	InsertTimeLineID;
 	TimeLineID	PrevTimeLineID;
 
 	/*
@@ -1051,10 +1047,10 @@ XLogInsertRecord(XLogRecData *rdata,
 		elog(ERROR, "cannot make new WAL entries during recovery");
 
 	/*
-	 * Given that we're not in recovery, ThisTimeLineID is set and can't
+	 * Given that we're not in recovery, InsertTimeLineID is set and can't
 	 * change, so we can read it without a lock.
 	 */
-	insertTLI = XLogCtl->ThisTimeLineID;
+	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/*----------
 	 *
@@ -2921,7 +2917,7 @@ XLogFlush(XLogRecPtr record)
 {
 	XLogRecPtr	WriteRqstPtr;
 	XLogwrtRqst WriteRqst;
-	TimeLineID	insertTLI = XLogCtl->ThisTimeLineID;
+	TimeLineID	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/*
 	 * During REDO, we are reading not writing WAL.  Therefore, instead of
@@ -3121,10 +3117,10 @@ XLogBackgroundFlush(void)
 		return false;
 
 	/*
-	 * Since we're not in recovery, ThisTimeLineID is set and can't change,
+	 * Since we're not in recovery, InsertTimeLineID is set and can't change,
 	 * so we can read it without a lock.
 	 */
-	insertTLI = XLogCtl->ThisTimeLineID;
+	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/* read LogwrtResult and update local state */
 	SpinLockAcquire(&XLogCtl->info_lck);
@@ -4165,7 +4161,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 	/*
 	 * Construct a filename of the last segment to be kept. The timeline ID
 	 * doesn't matter, we ignore that in the comparison. (During recovery,
-	 * ThisTimeLineID isn't set, so we can't use that.)
+	 * InsertTimeLineID isn't set, so we can't use that.)
 	 */
 	XLogFileName(lastoff, 0, segno, wal_segment_size);
 
@@ -6686,8 +6682,8 @@ StartupXLOG(void)
 				checkPointLoc,
 				EndOfLog;
 	TimeLineID	EndOfLogTLI;
-	TimeLineID	ThisTimeLineID,
-				PrevTimeLineID;
+	TimeLineID	replayTLI,
+				newTLI;
 	XLogRecord *record;
 	TransactionId oldestActiveXID;
 	bool		backupEndRequired = false;
@@ -6882,7 +6878,7 @@ StartupXLOG(void)
 	replay_image_masked = (char *) palloc(BLCKSZ);
 	primary_image_masked = (char *) palloc(BLCKSZ);
 
-	if (read_backup_label(&checkPointLoc, &ThisTimeLineID, &backupEndRequired,
+	if (read_backup_label(&checkPointLoc, &replayTLI, &backupEndRequired,
 						  &backupFromStandby))
 	{
 		List	   *tablespaces = NIL;
@@ -6901,7 +6897,7 @@ StartupXLOG(void)
 		 * the checkpoint it identifies, rather than using pg_control.
 		 */
 		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 0, true,
-									  ThisTimeLineID);
+									  replayTLI);
 		if (record != NULL)
 		{
 			memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint));
@@ -7037,9 +7033,9 @@ StartupXLOG(void)
 		/* Get the last valid checkpoint record. */
 		checkPointLoc = ControlFile->checkPoint;
 		RedoStartLSN = ControlFile->checkPointCopy.redo;
-		ThisTimeLineID = ControlFile->checkPointCopy.ThisTimeLineID;
+		replayTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true,
-									  ThisTimeLineID);
+									  replayTLI);
 		if (record != NULL)
 		{
 			ereport(DEBUG1,
@@ -7206,7 +7202,7 @@ StartupXLOG(void)
 	 * under, so temporarily adopt the TLI indicated by the checkpoint (see
 	 * also xlog_redo()).
 	 */
-	ThisTimeLineID = checkPoint.ThisTimeLineID;
+	replayTLI = checkPoint.ThisTimeLineID;
 
 	/*
 	 * Copy any missing timeline history files between 'now' and the recovery
@@ -7220,7 +7216,7 @@ StartupXLOG(void)
 	 * are small, so it's better to copy them unnecessarily than not copy them
 	 * and regret later.
 	 */
-	restoreTimeLineHistoryFiles(ThisTimeLineID, recoveryTargetTLI);
+	restoreTimeLineHistoryFiles(replayTLI, recoveryTargetTLI);
 
 	/*
 	 * Before running in recovery, scan pg_twophase and fill in its status to
@@ -7504,7 +7500,7 @@ StartupXLOG(void)
 			XLogCtl->replayEndRecPtr = checkPoint.redo;
 		else
 			XLogCtl->replayEndRecPtr = EndRecPtr;
-		XLogCtl->replayEndTLI = ThisTimeLineID;
+		XLogCtl->replayEndTLI = replayTLI;
 		XLogCtl->lastReplayedEndRecPtr = XLogCtl->replayEndRecPtr;
 		XLogCtl->lastReplayedTLI = XLogCtl->replayEndTLI;
 		XLogCtl->recoveryLastXTime = 0;
@@ -7539,12 +7535,12 @@ StartupXLOG(void)
 		{
 			/* back up to find the record */
 			XLogBeginRead(xlogreader, checkPoint.redo);
-			record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
+			record = ReadRecord(xlogreader, PANIC, false, replayTLI);
 		}
 		else
 		{
 			/* just have to read next record after CheckPoint */
-			record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
+			record = ReadRecord(xlogreader, LOG, false, replayTLI);
 		}
 
 		if (record != NULL)
@@ -7657,15 +7653,15 @@ StartupXLOG(void)
 				 * Before replaying this record, check if this record causes
 				 * the current timeline to change. The record is already
 				 * considered to be part of the new timeline, so we update
-				 * ThisTimeLineID before replaying it. That's important so
+				 * replayTLI 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)
 				{
-					TimeLineID	newTLI = ThisTimeLineID;
-					TimeLineID	prevTLI = ThisTimeLineID;
+					TimeLineID	newTLI = replayTLI;
+					TimeLineID	prevTLI = replayTLI;
 					uint8		info = record->xl_info & ~XLR_INFO_MASK;
 
 					if (info == XLOG_CHECKPOINT_SHUTDOWN)
@@ -7685,14 +7681,14 @@ StartupXLOG(void)
 						prevTLI = xlrec.PrevTimeLineID;
 					}
 
-					if (newTLI != ThisTimeLineID)
+					if (newTLI != replayTLI)
 					{
 						/* Check that it's OK to switch to this TLI */
 						checkTimeLineSwitch(EndRecPtr, newTLI, prevTLI,
-											ThisTimeLineID);
+											replayTLI);
 
 						/* Following WAL records should be run with new TLI */
-						ThisTimeLineID = newTLI;
+						replayTLI = newTLI;
 						switchedTLI = true;
 					}
 				}
@@ -7703,7 +7699,7 @@ StartupXLOG(void)
 				 */
 				SpinLockAcquire(&XLogCtl->info_lck);
 				XLogCtl->replayEndRecPtr = EndRecPtr;
-				XLogCtl->replayEndTLI = ThisTimeLineID;
+				XLogCtl->replayEndTLI = replayTLI;
 				SpinLockRelease(&XLogCtl->info_lck);
 
 				/*
@@ -7735,7 +7731,7 @@ StartupXLOG(void)
 				 */
 				SpinLockAcquire(&XLogCtl->info_lck);
 				XLogCtl->lastReplayedEndRecPtr = EndRecPtr;
-				XLogCtl->lastReplayedTLI = ThisTimeLineID;
+				XLogCtl->lastReplayedTLI = replayTLI;
 				SpinLockRelease(&XLogCtl->info_lck);
 
 				/*
@@ -7763,7 +7759,7 @@ StartupXLOG(void)
 					 * (possibly bogus) future WAL segments on the old
 					 * timeline.
 					 */
-					RemoveNonParentXlogFiles(EndRecPtr, ThisTimeLineID);
+					RemoveNonParentXlogFiles(EndRecPtr, replayTLI);
 
 					/*
 					 * Wake up any walsenders to notice that we are on a new
@@ -7781,7 +7777,7 @@ StartupXLOG(void)
 				}
 
 				/* Else, try to fetch the next WAL record */
-				record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
+				record = ReadRecord(xlogreader, LOG, false, replayTLI);
 			} while (record != NULL);
 
 			/*
@@ -7905,7 +7901,7 @@ StartupXLOG(void)
 	 * what we consider the valid portion of WAL.
 	 */
 	XLogBeginRead(xlogreader, LastRec);
-	record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
+	record = ReadRecord(xlogreader, PANIC, false, replayTLI);
 	EndOfLog = EndRecPtr;
 
 	/*
@@ -7982,7 +7978,7 @@ StartupXLOG(void)
 	 *
 	 * In a normal crash recovery, we can just extend the timeline we were in.
 	 */
-	PrevTimeLineID = ThisTimeLineID;
+	newTLI = replayTLI;
 	if (ArchiveRecoveryRequested)
 	{
 		char	   *reason;
@@ -7990,9 +7986,9 @@ StartupXLOG(void)
 
 		Assert(InArchiveRecovery);
 
-		ThisTimeLineID = findNewestTimeLine(recoveryTargetTLI) + 1;
+		newTLI = findNewestTimeLine(recoveryTargetTLI) + 1;
 		ereport(LOG,
-				(errmsg("selected new timeline ID: %u", ThisTimeLineID)));
+				(errmsg("selected new timeline ID: %u", newTLI)));
 
 		reason = getRecoveryStopReason();
 
@@ -8002,7 +7998,7 @@ StartupXLOG(void)
 		 * (Note that we also have a copy of the last block of the old WAL in
 		 * readBuf; we will use that below.)
 		 */
-		exitArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
+		exitArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 		/*
 		 * Write the timeline history file, and have it archived. After this
@@ -8014,7 +8010,7 @@ StartupXLOG(void)
 		 * To minimize the window for that, try to do as little as possible
 		 * between here and writing the end-of-recovery record.
 		 */
-		writeTimeLineHistory(ThisTimeLineID, recoveryTargetTLI,
+		writeTimeLineHistory(newTLI, recoveryTargetTLI,
 							 EndRecPtr, reason);
 
 		/*
@@ -8030,8 +8026,8 @@ StartupXLOG(void)
 	}
 
 	/* Save the selected TimeLineID in shared memory, too */
-	XLogCtl->ThisTimeLineID = ThisTimeLineID;
-	XLogCtl->PrevTimeLineID = PrevTimeLineID;
+	XLogCtl->InsertTimeLineID = newTLI;
+	XLogCtl->PrevTimeLineID = replayTLI;
 
 	/*
 	 * Actually, if WAL ended in an incomplete record, skip the parts that
@@ -8100,7 +8096,7 @@ StartupXLOG(void)
 	/*
 	 * Preallocate additional log files, if wanted.
 	 */
-	PreallocXlogFiles(EndOfLog, ThisTimeLineID);
+	PreallocXlogFiles(EndOfLog, newTLI);
 
 	/*
 	 * Okay, we're officially UP.
@@ -8181,7 +8177,7 @@ StartupXLOG(void)
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
 	if (ArchiveRecoveryRequested)
-		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
+		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
 	 * Local WAL inserts enabled, so it's time to finish initialization of
@@ -8749,11 +8745,14 @@ GetInsertRecPtr(void)
 
 /*
  * GetFlushRecPtr -- Returns the current flush position, ie, the last WAL
- * position known to be fsync'd to disk.
+ * position known to be fsync'd to disk. This should only be used on a
+ * system that is known not to be in recovery.
  */
 XLogRecPtr
 GetFlushRecPtr(TimeLineID *insertTLI)
 {
+	Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
+
 	SpinLockAcquire(&XLogCtl->info_lck);
 	LogwrtResult = XLogCtl->LogwrtResult;
 	SpinLockRelease(&XLogCtl->info_lck);
@@ -8763,7 +8762,7 @@ GetFlushRecPtr(TimeLineID *insertTLI)
 	 * so no lock is required.
 	 */
 	if (insertTLI)
-		*insertTLI = XLogCtl->ThisTimeLineID;
+		*insertTLI = XLogCtl->InsertTimeLineID;
 
 	return LogwrtResult.Flush;
 }
@@ -8778,7 +8777,7 @@ GetWALInsertionTimeLine(void)
 	Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
 
 	/* Since the value can't be changing, no lock is required. */
-	return XLogCtl->ThisTimeLineID;
+	return XLogCtl->InsertTimeLineID;
 }
 
 /*
@@ -9224,7 +9223,7 @@ CreateCheckPoint(int flags)
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		oldXLogAllowed = LocalSetXLogInsertAllowed();
 
-	checkPoint.ThisTimeLineID = XLogCtl->ThisTimeLineID;
+	checkPoint.ThisTimeLineID = XLogCtl->InsertTimeLineID;
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		checkPoint.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	else
@@ -9539,7 +9538,7 @@ CreateEndOfRecoveryRecord(void)
 	xlrec.end_time = GetCurrentTimestamp();
 
 	WALInsertLockAcquireExclusive();
-	xlrec.ThisTimeLineID = XLogCtl->ThisTimeLineID;
+	xlrec.ThisTimeLineID = XLogCtl->InsertTimeLineID;
 	xlrec.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	WALInsertLockRelease();
 
@@ -9891,7 +9890,7 @@ CreateRestartPoint(int flags)
 	 * with that.
 	 */
 	if (!RecoveryInProgress())
-		replayTLI = XLogCtl->ThisTimeLineID;
+		replayTLI = XLogCtl->InsertTimeLineID;
 
 	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
 
@@ -11811,10 +11810,10 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 		stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
 
 		/*
-		 * Given that we're not in recovery, ThisTimeLineID is set and can't
+		 * Given that we're not in recovery, InsertTimeLineID is set and can't
 		 * change, so we can read it without a lock.
 		 */
-		stoptli = XLogCtl->ThisTimeLineID;
+		stoptli = XLogCtl->InsertTimeLineID;
 
 		/*
 		 * Force a switch to a new xlog segment file, so that the backup is
-- 
2.24.3 (Apple Git-128)

#11Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#10)
Re: removing global variable ThisTimeLineID

On Mon, Nov 08, 2021 at 12:49:52PM -0500, Robert Haas wrote:

Even with this patch, the name ThisTimeLineID is still used for
multiple purposes. It remains part of the CheckPoint struct, and also
part of the xl_end_of_recovery struct. But in both of those cases, the
name ThisTimeLineID actually makes sense, because what we're thinking
about is whether there's a timeline change, so we have ThisTimeLineID
and PrevTimeLineID and it seems fairly clear what that's supposed to
mean.

I got to wonder if it would be better to bite the bullet here for
these two looking at your patch, but I am fine to keep things as they
are, as well.

Thoughts?

I think that this patch is an improvement.

@@ -6686,8 +6682,8 @@ StartupXLOG(void)
-   TimeLineID  ThisTimeLineID,
-               PrevTimeLineID;
+   TimeLineID  replayTLI,
+               newTLI;
One problem with newTLI is that this conflicts with the declaration
around 7663 in xlog.c, where we check after a TLI switch when
replaying such a record.  Perhaps this could be named newInsertTLI,
for example.
--
Michael
#12Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#11)
1 attachment(s)
Re: removing global variable ThisTimeLineID

On Mon, Nov 8, 2021 at 10:31 PM Michael Paquier <michael@paquier.xyz> wrote:

I think that this patch is an improvement.

Cool.

@@ -6686,8 +6682,8 @@ StartupXLOG(void)
-   TimeLineID  ThisTimeLineID,
-               PrevTimeLineID;
+   TimeLineID  replayTLI,
+               newTLI;
One problem with newTLI is that this conflicts with the declaration
around 7663 in xlog.c, where we check after a TLI switch when
replaying such a record.  Perhaps this could be named newInsertTLI,
for example.

That's a good point. However, since I think newTLI is already in use
in some of the functions StartupXLOG() calls, and since I think it
would be good to use the same name in StartupXLOG() as in the
functions that it calls, what I'd prefer to do is rename the newTLI
variable in the inner scope to newReplayTLI, as in the attached v2.

--
Robert Haas
EDB: http://www.enterprisedb.com

Attachments:

v2-0001-More-cleanup-of-ThisTimeLineID.patchapplication/octet-stream; name=v2-0001-More-cleanup-of-ThisTimeLineID.patchDownload
From 205f3e50b00669bf91146a62265463a9f69be7f3 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 9 Nov 2021 14:13:46 -0500
Subject: [PATCH v2] More cleanup of 'ThisTimeLineID'.

In XLogCtlData, rename the structure member ThisTimeLineID to
InsertTimeLineID and update the comments to make clear that it's only
expected to be set after recovery is complete.

In StartupXLOG, replace the local variables ThisTimeLineID and
PrevTimeLineID with new local variables replayTLI and newTLI.  In the
old scheme, ThisTimeLineID was the replay TLI until we created a new
timeline, and after that the replay TLI was in PrevTimeLineID. Now,
replayTLI is the TLI from which we last replayed WAL throughout the
entire function, and newTLI is either that, or the new timeline created
upon promotion.

Remove some misleading comments from the comment block just above where
recoveryTargetTimeLineGoal and friends are declared. It's become
incorrect, not only because ThisTimeLineID as a variable is now gone,
but also because the rmgr code does not care about ThisTimeLineID and
has not since what used to be the TLI field in the page header was
repurposed to store the page checksum.

Add a comment GetFlushRecPtr that it's only supposed to be used in
normal running, and an assertion to verify that this is so.

Per some ideas from Michael Paquier and some of my own. Review by
Michael Paquier also.

Discussion: http://postgr.es/m/CA+TgmoY1a2d1AnVR3tJcKmGGkhj7GGrwiNwjtKr21dxOuLBzCQ@mail.gmail.com
---
 src/backend/access/transam/xlog.c | 125 +++++++++++++++---------------
 1 file changed, 62 insertions(+), 63 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5cda30836f..e073121a7e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -301,13 +301,6 @@ static char recoveryStopName[MAXFNAMELEN];
 static bool recoveryStopAfter;
 
 /*
- * During normal operation, the only timeline we care about is ThisTimeLineID.
- * During recovery, however, things are more complicated.  To simplify life
- * for rmgr code, we keep ThisTimeLineID set to the "current" timeline as we
- * scan through the WAL history (that is, it is the line that was active when
- * the currently-scanned WAL record was generated).  We also need these
- * timeline values:
- *
  * recoveryTargetTimeLineGoal: what the user requested, if any
  *
  * recoveryTargetTLIRequested: numeric value of requested timeline, if constant
@@ -321,8 +314,9 @@ static bool recoveryStopAfter;
  * candidate WAL files to open at all.
  *
  * curFileTLI: the TLI appearing in the name of the current input WAL file.
- * (This is not necessarily the same as ThisTimeLineID, because we could
- * be scanning data that was copied from an ancestor timeline when the current
+ * (This is not necessarily the same as the timeline from which we are
+ * replaying WAL, which StartupXLOG calls replayTLI, because we could be
+ * scanning data that was copied from an ancestor timeline when the current
  * file was created.)  During a sequential scan we do not allow this value
  * to decrease.
  */
@@ -630,12 +624,14 @@ typedef struct XLogCtlData
 	int			XLogCacheBlck;	/* highest allocated xlog buffer index */
 
 	/*
-	 * Shared copy of ThisTimeLineID. Does not change after end-of-recovery.
-	 * If we created a new timeline when the system was started up,
+	 * InsertTimeLineID is the timeline into which new WAL is being inserted
+	 * and flushed. It is zero during recovery, and does not change once set.
+	 *
+	 * If we create a new timeline when the system was started up,
 	 * PrevTimeLineID is the old timeline's ID that we forked off from.
-	 * Otherwise it's equal to ThisTimeLineID.
+	 * Otherwise it's equal to InsertTimeLineID.
 	 */
-	TimeLineID	ThisTimeLineID;
+	TimeLineID	InsertTimeLineID;
 	TimeLineID	PrevTimeLineID;
 
 	/*
@@ -1051,10 +1047,10 @@ XLogInsertRecord(XLogRecData *rdata,
 		elog(ERROR, "cannot make new WAL entries during recovery");
 
 	/*
-	 * Given that we're not in recovery, ThisTimeLineID is set and can't
+	 * Given that we're not in recovery, InsertTimeLineID is set and can't
 	 * change, so we can read it without a lock.
 	 */
-	insertTLI = XLogCtl->ThisTimeLineID;
+	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/*----------
 	 *
@@ -2921,7 +2917,7 @@ XLogFlush(XLogRecPtr record)
 {
 	XLogRecPtr	WriteRqstPtr;
 	XLogwrtRqst WriteRqst;
-	TimeLineID	insertTLI = XLogCtl->ThisTimeLineID;
+	TimeLineID	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/*
 	 * During REDO, we are reading not writing WAL.  Therefore, instead of
@@ -3121,10 +3117,10 @@ XLogBackgroundFlush(void)
 		return false;
 
 	/*
-	 * Since we're not in recovery, ThisTimeLineID is set and can't change,
+	 * Since we're not in recovery, InsertTimeLineID is set and can't change,
 	 * so we can read it without a lock.
 	 */
-	insertTLI = XLogCtl->ThisTimeLineID;
+	insertTLI = XLogCtl->InsertTimeLineID;
 
 	/* read LogwrtResult and update local state */
 	SpinLockAcquire(&XLogCtl->info_lck);
@@ -4165,7 +4161,7 @@ RemoveOldXlogFiles(XLogSegNo segno, XLogRecPtr lastredoptr, XLogRecPtr endptr,
 	/*
 	 * Construct a filename of the last segment to be kept. The timeline ID
 	 * doesn't matter, we ignore that in the comparison. (During recovery,
-	 * ThisTimeLineID isn't set, so we can't use that.)
+	 * InsertTimeLineID isn't set, so we can't use that.)
 	 */
 	XLogFileName(lastoff, 0, segno, wal_segment_size);
 
@@ -6686,8 +6682,8 @@ StartupXLOG(void)
 				checkPointLoc,
 				EndOfLog;
 	TimeLineID	EndOfLogTLI;
-	TimeLineID	ThisTimeLineID,
-				PrevTimeLineID;
+	TimeLineID	replayTLI,
+				newTLI;
 	XLogRecord *record;
 	TransactionId oldestActiveXID;
 	bool		backupEndRequired = false;
@@ -6882,7 +6878,7 @@ StartupXLOG(void)
 	replay_image_masked = (char *) palloc(BLCKSZ);
 	primary_image_masked = (char *) palloc(BLCKSZ);
 
-	if (read_backup_label(&checkPointLoc, &ThisTimeLineID, &backupEndRequired,
+	if (read_backup_label(&checkPointLoc, &replayTLI, &backupEndRequired,
 						  &backupFromStandby))
 	{
 		List	   *tablespaces = NIL;
@@ -6901,7 +6897,7 @@ StartupXLOG(void)
 		 * the checkpoint it identifies, rather than using pg_control.
 		 */
 		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 0, true,
-									  ThisTimeLineID);
+									  replayTLI);
 		if (record != NULL)
 		{
 			memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint));
@@ -7037,9 +7033,9 @@ StartupXLOG(void)
 		/* Get the last valid checkpoint record. */
 		checkPointLoc = ControlFile->checkPoint;
 		RedoStartLSN = ControlFile->checkPointCopy.redo;
-		ThisTimeLineID = ControlFile->checkPointCopy.ThisTimeLineID;
+		replayTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 		record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, true,
-									  ThisTimeLineID);
+									  replayTLI);
 		if (record != NULL)
 		{
 			ereport(DEBUG1,
@@ -7206,7 +7202,7 @@ StartupXLOG(void)
 	 * under, so temporarily adopt the TLI indicated by the checkpoint (see
 	 * also xlog_redo()).
 	 */
-	ThisTimeLineID = checkPoint.ThisTimeLineID;
+	replayTLI = checkPoint.ThisTimeLineID;
 
 	/*
 	 * Copy any missing timeline history files between 'now' and the recovery
@@ -7220,7 +7216,7 @@ StartupXLOG(void)
 	 * are small, so it's better to copy them unnecessarily than not copy them
 	 * and regret later.
 	 */
-	restoreTimeLineHistoryFiles(ThisTimeLineID, recoveryTargetTLI);
+	restoreTimeLineHistoryFiles(replayTLI, recoveryTargetTLI);
 
 	/*
 	 * Before running in recovery, scan pg_twophase and fill in its status to
@@ -7504,7 +7500,7 @@ StartupXLOG(void)
 			XLogCtl->replayEndRecPtr = checkPoint.redo;
 		else
 			XLogCtl->replayEndRecPtr = EndRecPtr;
-		XLogCtl->replayEndTLI = ThisTimeLineID;
+		XLogCtl->replayEndTLI = replayTLI;
 		XLogCtl->lastReplayedEndRecPtr = XLogCtl->replayEndRecPtr;
 		XLogCtl->lastReplayedTLI = XLogCtl->replayEndTLI;
 		XLogCtl->recoveryLastXTime = 0;
@@ -7539,12 +7535,12 @@ StartupXLOG(void)
 		{
 			/* back up to find the record */
 			XLogBeginRead(xlogreader, checkPoint.redo);
-			record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
+			record = ReadRecord(xlogreader, PANIC, false, replayTLI);
 		}
 		else
 		{
 			/* just have to read next record after CheckPoint */
-			record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
+			record = ReadRecord(xlogreader, LOG, false, replayTLI);
 		}
 
 		if (record != NULL)
@@ -7657,15 +7653,15 @@ StartupXLOG(void)
 				 * Before replaying this record, check if this record causes
 				 * the current timeline to change. The record is already
 				 * considered to be part of the new timeline, so we update
-				 * ThisTimeLineID before replaying it. That's important so
+				 * replayTLI 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)
 				{
-					TimeLineID	newTLI = ThisTimeLineID;
-					TimeLineID	prevTLI = ThisTimeLineID;
+					TimeLineID	newReplayTLI = replayTLI;
+					TimeLineID	prevReplayTLI = replayTLI;
 					uint8		info = record->xl_info & ~XLR_INFO_MASK;
 
 					if (info == XLOG_CHECKPOINT_SHUTDOWN)
@@ -7673,26 +7669,26 @@ StartupXLOG(void)
 						CheckPoint	checkPoint;
 
 						memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint));
-						newTLI = checkPoint.ThisTimeLineID;
-						prevTLI = checkPoint.PrevTimeLineID;
+						newReplayTLI = checkPoint.ThisTimeLineID;
+						prevReplayTLI = checkPoint.PrevTimeLineID;
 					}
 					else if (info == XLOG_END_OF_RECOVERY)
 					{
 						xl_end_of_recovery xlrec;
 
 						memcpy(&xlrec, XLogRecGetData(xlogreader), sizeof(xl_end_of_recovery));
-						newTLI = xlrec.ThisTimeLineID;
-						prevTLI = xlrec.PrevTimeLineID;
+						newReplayTLI = xlrec.ThisTimeLineID;
+						prevReplayTLI = xlrec.PrevTimeLineID;
 					}
 
-					if (newTLI != ThisTimeLineID)
+					if (newReplayTLI != replayTLI)
 					{
 						/* Check that it's OK to switch to this TLI */
-						checkTimeLineSwitch(EndRecPtr, newTLI, prevTLI,
-											ThisTimeLineID);
+						checkTimeLineSwitch(EndRecPtr, newReplayTLI,
+											prevReplayTLI, replayTLI);
 
 						/* Following WAL records should be run with new TLI */
-						ThisTimeLineID = newTLI;
+						replayTLI = newReplayTLI;
 						switchedTLI = true;
 					}
 				}
@@ -7703,7 +7699,7 @@ StartupXLOG(void)
 				 */
 				SpinLockAcquire(&XLogCtl->info_lck);
 				XLogCtl->replayEndRecPtr = EndRecPtr;
-				XLogCtl->replayEndTLI = ThisTimeLineID;
+				XLogCtl->replayEndTLI = replayTLI;
 				SpinLockRelease(&XLogCtl->info_lck);
 
 				/*
@@ -7735,7 +7731,7 @@ StartupXLOG(void)
 				 */
 				SpinLockAcquire(&XLogCtl->info_lck);
 				XLogCtl->lastReplayedEndRecPtr = EndRecPtr;
-				XLogCtl->lastReplayedTLI = ThisTimeLineID;
+				XLogCtl->lastReplayedTLI = replayTLI;
 				SpinLockRelease(&XLogCtl->info_lck);
 
 				/*
@@ -7763,7 +7759,7 @@ StartupXLOG(void)
 					 * (possibly bogus) future WAL segments on the old
 					 * timeline.
 					 */
-					RemoveNonParentXlogFiles(EndRecPtr, ThisTimeLineID);
+					RemoveNonParentXlogFiles(EndRecPtr, replayTLI);
 
 					/*
 					 * Wake up any walsenders to notice that we are on a new
@@ -7781,7 +7777,7 @@ StartupXLOG(void)
 				}
 
 				/* Else, try to fetch the next WAL record */
-				record = ReadRecord(xlogreader, LOG, false, ThisTimeLineID);
+				record = ReadRecord(xlogreader, LOG, false, replayTLI);
 			} while (record != NULL);
 
 			/*
@@ -7905,7 +7901,7 @@ StartupXLOG(void)
 	 * what we consider the valid portion of WAL.
 	 */
 	XLogBeginRead(xlogreader, LastRec);
-	record = ReadRecord(xlogreader, PANIC, false, ThisTimeLineID);
+	record = ReadRecord(xlogreader, PANIC, false, replayTLI);
 	EndOfLog = EndRecPtr;
 
 	/*
@@ -7982,7 +7978,7 @@ StartupXLOG(void)
 	 *
 	 * In a normal crash recovery, we can just extend the timeline we were in.
 	 */
-	PrevTimeLineID = ThisTimeLineID;
+	newTLI = replayTLI;
 	if (ArchiveRecoveryRequested)
 	{
 		char	   *reason;
@@ -7990,9 +7986,9 @@ StartupXLOG(void)
 
 		Assert(InArchiveRecovery);
 
-		ThisTimeLineID = findNewestTimeLine(recoveryTargetTLI) + 1;
+		newTLI = findNewestTimeLine(recoveryTargetTLI) + 1;
 		ereport(LOG,
-				(errmsg("selected new timeline ID: %u", ThisTimeLineID)));
+				(errmsg("selected new timeline ID: %u", newTLI)));
 
 		reason = getRecoveryStopReason();
 
@@ -8002,7 +7998,7 @@ StartupXLOG(void)
 		 * (Note that we also have a copy of the last block of the old WAL in
 		 * readBuf; we will use that below.)
 		 */
-		exitArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
+		exitArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 		/*
 		 * Write the timeline history file, and have it archived. After this
@@ -8014,7 +8010,7 @@ StartupXLOG(void)
 		 * To minimize the window for that, try to do as little as possible
 		 * between here and writing the end-of-recovery record.
 		 */
-		writeTimeLineHistory(ThisTimeLineID, recoveryTargetTLI,
+		writeTimeLineHistory(newTLI, recoveryTargetTLI,
 							 EndRecPtr, reason);
 
 		/*
@@ -8030,8 +8026,8 @@ StartupXLOG(void)
 	}
 
 	/* Save the selected TimeLineID in shared memory, too */
-	XLogCtl->ThisTimeLineID = ThisTimeLineID;
-	XLogCtl->PrevTimeLineID = PrevTimeLineID;
+	XLogCtl->InsertTimeLineID = newTLI;
+	XLogCtl->PrevTimeLineID = replayTLI;
 
 	/*
 	 * Actually, if WAL ended in an incomplete record, skip the parts that
@@ -8100,7 +8096,7 @@ StartupXLOG(void)
 	/*
 	 * Preallocate additional log files, if wanted.
 	 */
-	PreallocXlogFiles(EndOfLog, ThisTimeLineID);
+	PreallocXlogFiles(EndOfLog, newTLI);
 
 	/*
 	 * Okay, we're officially UP.
@@ -8181,7 +8177,7 @@ StartupXLOG(void)
 
 	/* If this is archive recovery, perform post-recovery cleanup actions. */
 	if (ArchiveRecoveryRequested)
-		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, ThisTimeLineID);
+		CleanupAfterArchiveRecovery(EndOfLogTLI, EndOfLog, newTLI);
 
 	/*
 	 * Local WAL inserts enabled, so it's time to finish initialization of
@@ -8749,11 +8745,14 @@ GetInsertRecPtr(void)
 
 /*
  * GetFlushRecPtr -- Returns the current flush position, ie, the last WAL
- * position known to be fsync'd to disk.
+ * position known to be fsync'd to disk. This should only be used on a
+ * system that is known not to be in recovery.
  */
 XLogRecPtr
 GetFlushRecPtr(TimeLineID *insertTLI)
 {
+	Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
+
 	SpinLockAcquire(&XLogCtl->info_lck);
 	LogwrtResult = XLogCtl->LogwrtResult;
 	SpinLockRelease(&XLogCtl->info_lck);
@@ -8763,7 +8762,7 @@ GetFlushRecPtr(TimeLineID *insertTLI)
 	 * so no lock is required.
 	 */
 	if (insertTLI)
-		*insertTLI = XLogCtl->ThisTimeLineID;
+		*insertTLI = XLogCtl->InsertTimeLineID;
 
 	return LogwrtResult.Flush;
 }
@@ -8778,7 +8777,7 @@ GetWALInsertionTimeLine(void)
 	Assert(XLogCtl->SharedRecoveryState == RECOVERY_STATE_DONE);
 
 	/* Since the value can't be changing, no lock is required. */
-	return XLogCtl->ThisTimeLineID;
+	return XLogCtl->InsertTimeLineID;
 }
 
 /*
@@ -9224,7 +9223,7 @@ CreateCheckPoint(int flags)
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		oldXLogAllowed = LocalSetXLogInsertAllowed();
 
-	checkPoint.ThisTimeLineID = XLogCtl->ThisTimeLineID;
+	checkPoint.ThisTimeLineID = XLogCtl->InsertTimeLineID;
 	if (flags & CHECKPOINT_END_OF_RECOVERY)
 		checkPoint.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	else
@@ -9539,7 +9538,7 @@ CreateEndOfRecoveryRecord(void)
 	xlrec.end_time = GetCurrentTimestamp();
 
 	WALInsertLockAcquireExclusive();
-	xlrec.ThisTimeLineID = XLogCtl->ThisTimeLineID;
+	xlrec.ThisTimeLineID = XLogCtl->InsertTimeLineID;
 	xlrec.PrevTimeLineID = XLogCtl->PrevTimeLineID;
 	WALInsertLockRelease();
 
@@ -9891,7 +9890,7 @@ CreateRestartPoint(int flags)
 	 * with that.
 	 */
 	if (!RecoveryInProgress())
-		replayTLI = XLogCtl->ThisTimeLineID;
+		replayTLI = XLogCtl->InsertTimeLineID;
 
 	RemoveOldXlogFiles(_logSegNo, RedoRecPtr, endptr, replayTLI);
 
@@ -11811,10 +11810,10 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
 		stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
 
 		/*
-		 * Given that we're not in recovery, ThisTimeLineID is set and can't
+		 * Given that we're not in recovery, InsertTimeLineID is set and can't
 		 * change, so we can read it without a lock.
 		 */
-		stoptli = XLogCtl->ThisTimeLineID;
+		stoptli = XLogCtl->InsertTimeLineID;
 
 		/*
 		 * Force a switch to a new xlog segment file, so that the backup is
-- 
2.24.3 (Apple Git-128)

#13Michael Paquier
michael@paquier.xyz
In reply to: Robert Haas (#12)
Re: removing global variable ThisTimeLineID

On Tue, Nov 09, 2021 at 02:15:51PM -0500, Robert Haas wrote:

That's a good point. However, since I think newTLI is already in use
in some of the functions StartupXLOG() calls, and since I think it
would be good to use the same name in StartupXLOG() as in the
functions that it calls, what I'd prefer to do is rename the newTLI
variable in the inner scope to newReplayTLI, as in the attached v2.

WFM. Thanks.
--
Michael

#14Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#13)
Re: removing global variable ThisTimeLineID

On Tue, Nov 9, 2021 at 7:41 PM Michael Paquier <michael@paquier.xyz> wrote:

On Tue, Nov 09, 2021 at 02:15:51PM -0500, Robert Haas wrote:

That's a good point. However, since I think newTLI is already in use
in some of the functions StartupXLOG() calls, and since I think it
would be good to use the same name in StartupXLOG() as in the
functions that it calls, what I'd prefer to do is rename the newTLI
variable in the inner scope to newReplayTLI, as in the attached v2.

WFM. Thanks.

Cool. Committed that way.

--
Robert Haas
EDB: http://www.enterprisedb.com

#15Drouvot, Bertrand
bdrouvot@amazon.com
In reply to: Robert Haas (#10)
Re: removing global variable ThisTimeLineID

Hi,

On 11/8/21 6:49 PM, Robert Haas wrote:

On Mon, Nov 8, 2021 at 8:27 AM Robert Haas <robertmhaas@gmail.com> wrote:
Well I went through and it seems to be OK: all the existing callers of
that function first verify that we're not in recovery. The patch to
make logical decoding work in standby mode might change that,

Indeed, I think the logical decoding on standby patch just needs to
change the Assert in GetWALInsertionTimeLine() to check
SharedRecoveryState is RECOVERY_STATE_DONE or RECOVERY_STATE_ARCHIVE.

Would that make sense from your point of view to add the check on
RECOVERY_STATE_ARCHIVE or do you think it would need to be more
"sophisticated"?

Thanks

Bertrand

#16Robert Haas
robertmhaas@gmail.com
In reply to: Drouvot, Bertrand (#15)
Re: removing global variable ThisTimeLineID

On Tue, Nov 23, 2021 at 4:36 AM Drouvot, Bertrand <bdrouvot@amazon.com> wrote:

Indeed, I think the logical decoding on standby patch just needs to
change the Assert in GetWALInsertionTimeLine() to check
SharedRecoveryState is RECOVERY_STATE_DONE or RECOVERY_STATE_ARCHIVE.

Would that make sense from your point of view to add the check on
RECOVERY_STATE_ARCHIVE or do you think it would need to be more
"sophisticated"?

Unless I'm confused, that would just be incorrect.
GetWALInsertionTimeLine() just returns InsertTimeLineID, which won't
be initialized during archive recovery. I discussed this point a bit
on some other thread with Andres. It's possible that all that you need
to do here is determine the replayTLI using e.g. GetXLogReplayRecPtr.
But I don't really see why that should be correct:
read_local_xlog_page() has some kind of wait-and-retry logic and I'm
not clear why we don't need the same thing here. After all, if we're
on the primary and we determine that sendTimeLineHistoric = false and
sendTimeLine = whatever, neither of those values can become incorrect
afterward. But on a standby that can certainly happen, if an upstream
server is promoted. Note that the comment in read_local_xlog_page()
says this:

* If that happens after our caller determined the TLI but before
* we actually read the xlog page, we might still try to read from the
* old (now renamed) segment and fail. There's not much we can do
* about this, but it can only happen when we're a leaf of a cascading
* standby whose primary gets promoted while we're decoding, so a
* one-off ERROR isn't too bad.

That seems to imply that the retry loop here is designed, at least in
part, to protect against exactly this kind of scenario.

--
Robert Haas
EDB: http://www.enterprisedb.com