From 5cfd98c132556aa6a95d74f7150a4048243e65e5 Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Tue, 17 Mar 2026 13:25:12 -0400
Subject: [PATCH v18 07/19] Add UnlockBufferGetLSN utility function.

This combines the functionality of BufferGetLSNAtomic() with
UnlockReleaseBuffer, with the option of not releasing buffer pin.

Author: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAH2-WzkyG01682zwqyUTwV=Zq+M_qGgi1NbXwp1H-piRSfJsgQ@mail.gmail.com
---
 src/include/storage/bufmgr.h          |  1 +
 src/backend/access/index/indexbatch.c | 12 +---
 src/backend/storage/buffer/bufmgr.c   | 90 +++++++++++++++++++++++++++
 3 files changed, 92 insertions(+), 11 deletions(-)

diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index dd41b92f9..681a3e56f 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -255,6 +255,7 @@ extern void WaitReadBuffers(ReadBuffersOperation *operation);
 
 extern void ReleaseBuffer(Buffer buffer);
 extern void UnlockReleaseBuffer(Buffer buffer);
+extern XLogRecPtr UnlockBufferGetLSN(Buffer buffer, bool release);
 extern bool BufferIsLockedByMe(Buffer buffer);
 extern bool BufferIsLockedByMeInMode(Buffer buffer, BufferLockMode mode);
 extern bool BufferIsDirty(Buffer buffer);
diff --git a/src/backend/access/index/indexbatch.c b/src/backend/access/index/indexbatch.c
index 3abf58453..565182430 100644
--- a/src/backend/access/index/indexbatch.c
+++ b/src/backend/access/index/indexbatch.c
@@ -491,17 +491,7 @@ indexam_util_batch_unlock(IndexScanDesc scan, IndexScanBatch batch, Buffer buf)
 		 * Non-immediate-unguard scans retain the pin; the table AM will call
 		 * amunguardbatch to drop the interlock when ready.
 		 */
-		batch->lsn = BufferGetLSNAtomic(buf);
-		if (scan->batchImmediateUnguard)
-		{
-			/* drop both the lock and the pin */
-			UnlockReleaseBuffer(buf);
-		}
-		else
-		{
-			/* just drop the lock (hold on to interlock pin) */
-			UnlockBuffer(buf);
-		}
+		batch->lsn = UnlockBufferGetLSN(buf, scan->batchImmediateUnguard);
 
 		/* If we released buffer pin, batch is now unguarded */
 		batch->isGuarded = !scan->batchImmediateUnguard;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index cd21ae3fc..4cdb565e4 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -5638,6 +5638,96 @@ UnlockReleaseBuffer(Buffer buffer)
 	RESUME_INTERRUPTS();
 }
 
+/*
+ * UnlockBufferGetLSN
+ *		Read a buffer's LSN, then unlock the buffer.  Optionally release the
+ *		pin too.
+ *
+ * This combines the functionality of BufferGetLSNAtomic() with
+ * UnlockReleaseBuffer, with the option of not releasing buffer pin.
+ *
+ * Used to unlock a buffer lock (and possibly to release its pin) when held on
+ * an index page.  This is needed frequently enough to justify specialization.
+ */
+XLogRecPtr
+UnlockBufferGetLSN(Buffer buffer, bool release)
+{
+	BufferDesc *buf_hdr;
+	Page		page;
+	XLogRecPtr	lsn;
+	int			mode;
+	uint64		sub;
+	uint64		lockstate;
+	PrivateRefCountEntry *ref;
+
+	Assert(BufferIsValid(buffer));
+	Assert(BufferIsPinned(buffer));
+
+	if (BufferIsLocal(buffer))
+	{
+		buf_hdr = GetLocalBufferDescriptor(-buffer - 1);
+		page = (Page) LocalBufHdrGetBlock(buf_hdr);
+		lsn = PageGetLSN(page);
+		if (release)
+			UnpinLocalBuffer(buffer);
+		return lsn;
+	}
+
+	buf_hdr = GetBufferDescriptor(buffer - 1);
+	page = (Page) BufHdrGetBlock(buf_hdr);
+
+#ifdef PG_HAVE_8BYTE_SINGLE_COPY_ATOMICITY
+	lsn = PageGetLSN(page);
+#else
+	lsn = BufferGetLSNAtomic(buffer);
+#endif
+
+	if (!release)
+	{
+		/* Just release the lock */
+		BufferLockUnlock(buffer, buf_hdr);
+		return lsn;
+	}
+
+	/* Release both lock and pin in one atomic op */
+	ResourceOwnerForgetBuffer(CurrentResourceOwner, buffer);
+
+	mode = BufferLockDisownInternal(buffer, buf_hdr);
+
+	/* compute state modification for lock release */
+	sub = BufferLockReleaseSub(mode);
+
+	/* compute state modification for pin release */
+	ref = GetPrivateRefCountEntry(buffer, false);
+	Assert(ref != NULL);
+	Assert(ref->data.refcount > 0);
+	ref->data.refcount--;
+
+	/* no more backend local pins, reduce shared pin count */
+	if (likely(ref->data.refcount == 0))
+	{
+		/* See comment in UnpinBufferNoOwner() */
+		VALGRIND_MAKE_MEM_NOACCESS(BufHdrGetBlock(buf_hdr), BLCKSZ);
+
+		sub |= BUF_REFCOUNT_ONE;
+		ForgetPrivateRefCountEntry(ref);
+	}
+
+	/* perform the lock and pin release in one atomic op */
+	lockstate = pg_atomic_sub_fetch_u64(&buf_hdr->state, sub);
+
+	/* wake up waiters for the lock */
+	BufferLockProcessRelease(buf_hdr, mode, lockstate);
+
+	/* wake up waiter for the pin release */
+	if (lockstate & BM_PIN_COUNT_WAITER)
+		WakePinCountWaiter(buf_hdr);
+
+	RESUME_INTERRUPTS();
+
+	return lsn;
+}
+
 /*
  * IncrBufferRefCount
  *		Increment the pin count on a buffer that we have *already* pinned
-- 
2.53.0

