From a76bfa0fd9e2c7feb9082ddf43e91d40c2cbec7a Mon Sep 17 00:00:00 2001 From: Alex Speller <1217+alexspeller@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:34:05 +0000 Subject: [PATCH v2] Fix lost wakeups in physical WAL senders Physical WAL senders could sleep with flushed WAL still waiting to be sent, delaying replication and synchronous commits until another event woke the sender. In the reported case, commits stalled for about ten seconds, until the standby's next periodic status report. Commit bc971f4025c changed WAL sender wakeups to use condition variables, so that only senders already on the waitlist are notified. However, XLogSendPhysical() checks the flush position before WalSndWait() joins the waitlist. A WAL flush between those steps could therefore go unnoticed, allowing the sender to sleep despite having more WAL to send. Fix this by rechecking the flush position after joining the waitlist and returning to the send loop if more WAL is available. For cascading senders, also check for promotion or a timeline change, since these require the sender to revisit the current timeline even if the flush position has not advanced. Backpatch to v16, where bc971f4025c introduced the condition-variable wakeup mechanism. Author: Alex Speller Reviewed-by: Fujii Masao Discussion: https://postgr.es/m/mvx5bs7q.33b99c32-67d8-4373-8611-7a8250fb2fd3@we.are.superhuman.com Backpatch-through: 16 --- src/backend/replication/walsender.c | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 9f21c7c8dfe..7c40d16c033 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -4143,7 +4143,47 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event) if (wait_event == WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION) ConditionVariablePrepareToSleep(&WalSndCtl->wal_confirm_rcv_cv); else if (MyWalSnd->kind == REPLICATION_KIND_PHYSICAL) + { ConditionVariablePrepareToSleep(&WalSndCtl->wal_flush_cv); + + /* + * Recheck the conditions tested by XLogSendPhysical() now that we're + * on the waitlist. Otherwise, we could miss a wakeup for a WAL flush, + * promotion, or timeline switch that occurred before we joined it. + */ + if (wait_event == WAIT_EVENT_WAL_SENDER_MAIN && + !sendTimeLineIsHistoric && !pq_is_send_pending()) + { + XLogRecPtr flushPtr; + bool becameHistoric = false; + + Assert(WalSndCaughtUp); + Assert(!streamingDoneSending); + + if (am_cascading_walsender) + { + TimeLineID flushTLI; + + flushPtr = GetStandbyFlushRecPtr(&flushTLI); + + /* + * Promotion or a timeline switch need not advance the flush + * pointer. Let XLogSendPhysical() handle the now-historic + * timeline. + */ + becameHistoric = !RecoveryInProgress() || + sendTimeLine != flushTLI; + } + else + flushPtr = GetFlushRecPtr(NULL); + + if (flushPtr > sentPtr || becameHistoric) + { + ConditionVariableCancelSleep(); + return; + } + } + } else if (MyWalSnd->kind == REPLICATION_KIND_LOGICAL) ConditionVariablePrepareToSleep(&WalSndCtl->wal_replay_cv); -- 2.56.0