From 16f5402418e4115c1308e64fa6b6d1e5db778789 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Mon, 8 Jun 2026 05:52:09 +0000 Subject: [PATCH v1 1/4] Fix race condition in logical decoding timeline selection during promotion 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 Author: Bertrand Drouvot 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