From 5b4994cc6c244ecc6eb6826f91a35cbd07a9f00e Mon Sep 17 00:00:00 2001
From: Anthony Hsu <erwaman@gmail.com>
Date: Sun, 6 Jul 2025 11:20:50 -0700
Subject: [PATCH v1] Set 1s WaitLatch timeout if standby limit has expired in
 ResolveRecoveryConflictWithBufferPin

---
 src/backend/storage/ipc/standby.c | 16 ++++++++++------
 src/backend/storage/lmgr/proc.c   | 19 ++++++++++++++++---
 src/include/storage/proc.h        |  2 ++
 3 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 7fa8d9247e0..ff9070aba35 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -793,12 +793,14 @@ void
 ResolveRecoveryConflictWithBufferPin(void)
 {
 	TimestampTz ltime;
+	bool standby_limit_expired;
 
 	Assert(InHotStandby);
 
 	ltime = GetStandbyLimitTime();
+	standby_limit_expired = GetCurrentTimestamp() >= ltime && ltime != 0;
 
-	if (GetCurrentTimestamp() >= ltime && ltime != 0)
+	if (standby_limit_expired)
 	{
 		/*
 		 * We're already behind, so clear a path as quickly as possible.
@@ -835,12 +837,14 @@ ResolveRecoveryConflictWithBufferPin(void)
 	 * Wait to be signaled by UnpinBuffer() or for the wait to be interrupted
 	 * by one of the timeouts established above.
 	 *
-	 * We assume that only UnpinBuffer() and the timeout requests established
-	 * 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 standby limit has already expired, we also set a 1s timeout. This
+	 * is to ensure the startup process is still woken up in a reasonable time
+	 * in case a new backend sneaks in and acquires a conflicting pin before the
+	 * original conflicting backends are able to cancel themselves and reduce
+	 * the pin count to 1 (which wakes up the startup process).
 	 */
-	ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+	ProcWaitForSignalWithTimeout(WAIT_EVENT_BUFFER_PIN,
+								 standby_limit_expired ? 1000 : 0);
 
 	if (got_standby_delay_timeout)
 		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e9ef0fbfe32..7114256e9ed 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -1965,16 +1965,29 @@ GetLockHoldersAndWaiters(LOCALLOCK *locallock, StringInfo lock_holders_sbuf,
 
 /*
  * ProcWaitForSignal - wait for a signal from another backend.
+ */
+void
+ProcWaitForSignal(uint32 wait_event_info)
+{
+	ProcWaitForSignalWithTimeout(wait_event_info, 0);
+}
+
+/*
+ * ProcWaitForSignalWithTimeout - wait for a signal from another backend with a
+ * timeout.
  *
  * As this uses the generic process latch the caller has to be robust against
  * unrelated wakeups: Always check that the desired state has occurred, and
  * wait again if not.
  */
 void
-ProcWaitForSignal(uint32 wait_event_info)
+ProcWaitForSignalWithTimeout(uint32 wait_event_info, long timeout_ms)
 {
-	(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-					 wait_event_info);
+	int wake_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+	if (timeout_ms > 0)
+		wake_events |= WL_TIMEOUT;
+
+	(void) WaitLatch(MyLatch, wake_events, timeout_ms, wait_event_info);
 	ResetLatch(MyLatch);
 	CHECK_FOR_INTERRUPTS();
 }
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 9f9b3fcfbf1..930510ca8e5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -505,6 +505,8 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 int *lockHoldersNum);
 
 extern void ProcWaitForSignal(uint32 wait_event_info);
+extern void ProcWaitForSignalWithTimeout(uint32 wait_event_info,
+										 long timeout_ms);
 extern void ProcSendSignal(ProcNumber procNumber);
 
 extern PGPROC *AuxiliaryPidGetProc(int pid);
-- 
2.50.0.727.gbf7dc18ff4-goog

