From a6ca82b00245c3abad7c5bcfb829aefadc36167e Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Wed, 2 Sep 2026 21:59:36 +0000
Subject: [PATCH v3 2/3] Make autoprewarm yield to conflicting lock requests.

The autoprewarm worker holds AccessShareLock on a relation while
reading all of its previously-dumped blocks back into shared
buffers. On a large relation this takes a long time, and any
concurrent operation needing a conflicting lock, such as TRUNCATE
or DROP, is blocked for the whole duration.

Have the worker periodically check whether a conflicting lock
request is waiting and, if so, give up the relation: release the
lock and move on to the next one. Prewarming is best-effort, and
once a conflicting strong lock is taken the recorded block
numbers may no longer be useful, so there is little value in
reacquiring the lock to finish. This mirrors how VACUUM behaves
during heap truncation. The check is throttled to keep its cost
negligible when nobody is waiting.

The foreground pg_prewarm() function is left unchanged, on the
same reasoning that a foreground VACUUM does not deprioritize
itself: its caller asked for a bounded amount of work and is
waiting on it.

Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Co-authored-by: Satyanarayana Narlapuram <satyanarlapuram@gmail.com>
Reviewed-by: Robert Haas <robertmhaas@gmail.com>
Reviewed-by: Palak Chaturvedi <chaturvedipalak1911@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/CAHg%2BQDfdoR%3D7iqEAvLW9qtzV0Sx1wp2FuALeamqcCdiVEmMF-Q%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c | 109 +++++++++++++++++++++++++------
 1 file changed, 89 insertions(+), 20 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index deb4c2671b5..0e909e34be2 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -31,6 +31,7 @@
 #include "access/relation.h"
 #include "access/xact.h"
 #include "pgstat.h"
+#include "portability/instr_time.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
@@ -39,6 +40,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
+#include "storage/lmgr.h"
 #include "storage/lwlock.h"
 #include "storage/procsignal.h"
 #include "storage/read_stream.h"
@@ -52,6 +54,14 @@
 
 #define AUTOPREWARM_FILE "autoprewarm.blocks"
 
+/*
+ * How often the prewarm loop checks for a conflicting lock request: probe the
+ * shared lock table at most once per interval (in ms), and only consult the
+ * clock once every so many blocks.
+ */
+#define PREWARM_LOCK_CHECK_INTERVAL		20	/* ms */
+#define PREWARM_LOCK_CHECK_BLOCKS		32
+
 /* Metadata for each block we dump. */
 typedef struct BlockInfoRecord
 {
@@ -493,6 +503,73 @@ apw_read_stream_next_block(ReadStream *stream,
 	return InvalidBlockNumber;
 }
 
+/*
+ * Prewarm the blocks of one fork by draining the read stream, and return true
+ * if a conflicting lock request showed up while doing so. On such a waiter we
+ * stop early rather than reacquiring the lock and resuming; prewarming is
+ * best-effort, and the caller gives up the relation to let the waiter proceed.
+ * The read stream is always shut down before returning.
+ */
+static bool
+apw_prewarm_blocks(Relation rel, struct AutoPrewarmReadStreamData *p)
+{
+	ReadStream *stream;
+	Buffer		buf;
+	instr_time	starttime;
+	int			blocks_since_check = 0;
+	bool		waiter_detected = false;
+
+	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
+										READ_STREAM_DEFAULT |
+										READ_STREAM_USE_BATCHING,
+										NULL,
+										rel,
+										p->forknum,
+										apw_read_stream_next_block,
+										p,
+										0);
+
+	INSTR_TIME_SET_CURRENT(starttime);
+
+	while ((buf = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
+	{
+		apw_state->prewarmed_blocks++;
+		ReleaseBuffer(buf);
+
+		/*
+		 * Check for a conflicting lock waiter, but keep the clock reads and
+		 * lock table probes rare: only look at the clock every
+		 * PREWARM_LOCK_CHECK_BLOCKS blocks, and only probe once
+		 * PREWARM_LOCK_CHECK_INTERVAL has elapsed since the last probe.
+		 */
+		if (++blocks_since_check >= PREWARM_LOCK_CHECK_BLOCKS)
+		{
+			instr_time	currenttime;
+			instr_time	elapsed;
+
+			blocks_since_check = 0;
+
+			INSTR_TIME_SET_CURRENT(currenttime);
+			elapsed = currenttime;
+			INSTR_TIME_SUBTRACT(elapsed, starttime);
+			if ((INSTR_TIME_GET_MICROSEC(elapsed) / 1000)
+				>= PREWARM_LOCK_CHECK_INTERVAL)
+			{
+				if (LockHasWaitersRelation(rel, AccessShareLock))
+				{
+					waiter_detected = true;
+					break;
+				}
+				starttime = currenttime;
+			}
+		}
+	}
+
+	read_stream_end(stream);
+
+	return waiter_detected;
+}
+
 /*
  * Prewarm all blocks for one database (and possibly also global objects, if
  * those got grouped with this database).
@@ -577,8 +654,6 @@ autoprewarm_database_main(Datum main_arg)
 			ForkNumber	forknum;
 			BlockNumber nblocks;
 			struct AutoPrewarmReadStreamData p;
-			ReadStream *stream;
-			Buffer		buf;
 
 			blk = block_info[i];
 
@@ -627,29 +702,23 @@ autoprewarm_database_main(Datum main_arg)
 					.nblocks = nblocks,
 			};
 
-			stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
-												READ_STREAM_DEFAULT |
-												READ_STREAM_USE_BATCHING,
-												NULL,
-												rel,
-												p.forknum,
-												apw_read_stream_next_block,
-												&p,
-												0);
-
 			/*
-			 * Loop until we've prewarmed all the blocks from this fork. The
-			 * read stream callback will check that we still have free buffers
-			 * before requesting each block from the read stream API.
+			 * On a conflicting lock waiter, give up the whole relation: skip
+			 * its remaining blocks and break out so we close it (releasing
+			 * the lock) and move on to the next one.
 			 */
-			while ((buf = read_stream_next_buffer(stream, NULL)) != InvalidBuffer)
+			if (apw_prewarm_blocks(rel, &p))
 			{
-				apw_state->prewarmed_blocks++;
-				ReleaseBuffer(buf);
+				for (i = p.pos; i < apw_state->prewarm_stop_idx; i++)
+				{
+					blk = block_info[i];
+					if (blk.tablespace != tablespace ||
+						blk.filenumber != filenumber)
+						break;
+				}
+				break;
 			}
 
-			read_stream_end(stream);
-
 			/*
 			 * Advance i past all the blocks just prewarmed. Note that the
 			 * callback might have advanced the index beyond the last valid
-- 
2.47.3

