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