From efd453bb47d2b80a1cbf48e1eb85212be979b2bc Mon Sep 17 00:00:00 2001
From: alterego655 <824662526@qq.com>
Date: Mon, 22 Jun 2026 16:03:31 +0800
Subject: [PATCH v1] Fix lost wakeup in LockBufferForCleanup()

After publishing BM_PIN_COUNT_WAITER, LockBufferForCleanup() assumed that
a future unpin would wake it.  That is no longer guaranteed after
5310fac6e0f, because an unpin can reduce the buffer refcount while
BM_LOCKED is set.

This creates a lost-wakeup race: if the last conflicting pin disappears
before the waiter bit becomes visible, the unpin does not signal the
waiter, and LockBufferForCleanup() can enter an unbounded wait even though
the cleanup-lock condition is already satisfied.

When the race occurs, the returned state from UnlockBufHdrExt() already
has refcount 1.  In that case, skip the wait path, clear the waiter marker
in the existing cleanup code, and retry.  This prevents VACUUM from
sleeping indefinitely after the cleanup-lock condition is already
satisfied.
---
 src/backend/storage/buffer/bufmgr.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d6c0cc1f6d4..8355f511a44 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -6743,11 +6743,19 @@ LockBufferForCleanup(Buffer buffer)
 		}
 		bufHdr->wait_backend_pgprocno = MyProcNumber;
 		PinCountWaitBuf = bufHdr;
-		UnlockBufHdrExt(bufHdr, buf_state,
-						BM_PIN_COUNT_WAITER, 0,
-						0);
+		buf_state = UnlockBufHdrExt(bufHdr, buf_state,
+									BM_PIN_COUNT_WAITER, 0,
+									0);
 		LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
 
+		/*
+		 * An unpin can reduce the refcount while BM_LOCKED is set. If the
+		 * last conflicting pin disappeared before BM_PIN_COUNT_WAITER became
+		 * visible, there is nobody left who is guaranteed to wake us.
+		 */
+		if (BUF_STATE_GET_REFCOUNT(buf_state) == 1)
+			goto cleanup_waiter;
+
 		/* Wait to be signaled by UnpinBuffer() */
 		if (InHotStandby)
 		{
@@ -6796,6 +6804,7 @@ LockBufferForCleanup(Buffer buffer)
 		else
 			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
 
+cleanup_waiter:
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
 		 * anymore, but ProcWaitForSignal() can return for other signals as
-- 
2.51.0

