From 3b722a7ab1d9cee205fb419fb53ac16f7a49b516 Mon Sep 17 00:00:00 2001 From: Anthony Hsu Date: Sun, 22 Feb 2026 18:26:01 +0000 Subject: [PATCH v3] Set 1s WaitLatch timeout if standby limit has expired in ResolveRecoveryConflictWithBufferPin Once the standby limit has expired, the startup process signals all backends once and then sleeps until UnpinBuffer() wakes it up. A backend that pins the buffer after it has processed the signal is never told to cancel, so replay can stay stuck for as long as that pin is held. Wake up once per second in that case, so that the signal is sent again. Discussion: https://postgr.es/m/CALQc50gi-Kw9m1r6hytf12473-fCECy=q9JtKS4ANeJFEyCBTw@mail.gmail.com --- src/backend/storage/ipc/standby.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 7f011e04990..3e08ca40743 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -26,6 +26,7 @@ #include "pgstat.h" #include "replication/slot.h" #include "storage/bufmgr.h" +#include "storage/latch.h" #include "storage/proc.h" #include "storage/procarray.h" #include "storage/sinvaladt.h" @@ -791,10 +792,13 @@ cleanup: * so we don't do a deadlock check right away ... only if we have had to wait * at least deadlock_timeout. */ +#define STANDBY_CONFLICT_RESEND_MS 1000 + void ResolveRecoveryConflictWithBufferPin(void) { TimestampTz ltime; + bool limit_expired = false; Assert(InHotStandby); @@ -806,6 +810,7 @@ ResolveRecoveryConflictWithBufferPin(void) * We're already behind, so clear a path as quickly as possible. */ SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN); + limit_expired = true; } else { @@ -841,8 +846,24 @@ ResolveRecoveryConflictWithBufferPin(void) * above can wake us up here. WakeupRecovery() called by walreceiver or * SIGHUP signal handler, etc cannot do that because it uses the different * latch from that ProcWaitForSignal() waits on. + * + * If the limit has already expired, no timeout is armed. A backend that + * pins the buffer after it has processed our signal would then never be + * told to cancel, and UnpinBuffer() only wakes us when the last other pin + * goes away. So wake up periodically, to let the caller recheck the + * buffer and come back here to resend the signal. */ - ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP); + if (limit_expired) + { + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + STANDBY_CONFLICT_RESEND_MS, + WAIT_EVENT_BUFFER_CLEANUP); + ResetLatch(MyLatch); + CHECK_FOR_INTERRUPTS(); + } + else + ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP); if (got_standby_delay_timeout) SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN); -- 2.37.1 (Apple Git-137.1)