agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
From: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Subject: [PATCH v1 1/4] Fix race condition in logical decoding timeline selection during promotion
Date: Mon, 8 Jun 2026 05:52:09 +0000

During promotion, there is a window where RecoveryInProgress() still
returns true but old timeline WAL segments have already been removed or
recycled by RemoveNonParentXlogFiles() in CleanupAfterArchiveRecovery().
This is because, in StartupXLOG(), WAL segments are cleaned up before
SharedRecoveryState transitions to RECOVERY_STATE_DONE.

If a walsender performing logical decoding calls logical_read_xlog_page()
during this window, it would get the old timeline from GetXLogReplayRecPtr(),
then attempt to open a WAL segment on that old timeline which no longer exists,
resulting in:

ERROR: requested WAL segment ... has already been removed

Fix by checking GetWALInsertionTimeLineIfSet() when RecoveryInProgress()
returns true. If InsertTimeLineID is already set (non-zero), the new timeline is
established and we use it directly, avoiding attempts to read from segments that
may have been removed.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by:
Discussion: https://postgr.es/m/7daef094-abf3-4672-bc23-3df4763b16a3%40gmail.com
---
 src/backend/replication/walsender.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)
 100.0% src/backend/replication/

diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 04aa770d981..e80ed052077 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1104,7 +1104,24 @@ logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int req
 	am_cascading_walsender = RecoveryInProgress();
 
 	if (am_cascading_walsender)
-		GetXLogReplayRecPtr(&currTLI);
+	{
+		TimeLineID	insertTLI;
+
+		/*
+		 * If the insertion timeline has already been set, use it.
+		 * InsertTimeLineID is set before old timeline WAL segments are
+		 * removed and before SharedRecoveryState flips to
+		 * RECOVERY_STATE_DONE. So there is a window where
+		 * RecoveryInProgress() still returns true but the old timeline's WAL
+		 * segments have already been removed or recycled. Using the insertion
+		 * timeline avoids attempting to read from those removed segments.
+		 */
+		insertTLI = GetWALInsertionTimeLineIfSet();
+		if (insertTLI != 0)
+			currTLI = insertTLI;
+		else
+			GetXLogReplayRecPtr(&currTLI);
+	}
 	else
 		currTLI = GetWALInsertionTimeLine();
 
-- 
2.34.1


--HitlKQoyB8givWgr
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v1-0002-Add-injection-point-test-for-logical-decoding-tim.patch"



view thread (1094+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: bertranddrouvot.pg@gmail.com
  Subject: Re: [PATCH v1 1/4] Fix race condition in logical decoding timeline selection during promotion
  In-Reply-To: <no-message-id-597730@localhost>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox