Hi,

On Mon, Sep 7, 2026 at 8:01 AM Palak Chaturvedi
<[email protected]> wrote:
>
> I found two other issues while reviewing v3.

Thanks for reviewing it.

> First, 0001 assumes that finding a LOCK in LockMethodLockHash means
> that this backend's fast-path lock has already been transferred and
> therefore has a PROCLOCK. I don't think that is guaranteed.
>
> For example, backend A can hold a weak relation lock through the fast
> path, while backend B acquires the same weak lock through the main
> lock table because its fast-path slots are full. In that case, the
> LOCK exists because of backend B, but backend A still has no PROCLOCK.
> If A calls LockHasWaiters(), 0001 finds the LOCK and then raises:
>
>   ERROR: failed to re-find shared proclock object

Ah, you are right. Thanks for catching that. I added a "failed to
re-find shared proclock object" error as a test case in the 0003
patch, in case it's useful. It seems like I didn't fully implement
what Robert suggested here:
https://postgr.es/m/CA%2BTgmob3mVc0LgKNtgy-MdDd9KLffzw1X%3D9qR8UaRmON0xJWNA%40mail.gmail.com.
Fixed in the attached v4, which returns false when our proclock isn't
there instead of erroring out.

> Second, the current CFBot run fails in the Linux 32-bit job. The
> 002_autoprewarm_lock_yield test sets:
>
>   shared_buffers = '2GB'
>
> The server then fails during startup with:
>
>   FATAL: invalid size -2147483648 for shared memory request for
>   "Buffer Blocks"
>
> 0003 describes the test as manual/local, but it is registered in the
> Meson and Make test suites, so CFBot runs it. It either needs a
> portable configuration, an early skip on unsupported builds, or
> should remain unregistered if it is only intended for manual use.

I reduced shared_buffers and relation size to 512MB and about 260MB
respectively and ran the test locally, so I'm not so sure if the CFBot
will be fully happy with it, so I chose to use nocfbot- prefix.

Please find the attached v4 patches.

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com
From b3449b3ac279ac3838f7a1cf21de3f1e7da64f45 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 13 Sep 2026 20:11:27 +0000
Subject: [PATCH v4] Fix LockHasWaiters() crash for fast-path locks.

A backend records every lock it holds in its own local lock
table, and when the lock also lives in the shared lock table, the
local entry remembers where to find it and the proclock standing
for this backend's hold on it. LockHasWaiters() assumed that it
always does, and crashed the backend when it did not. A lock
acquired through the fast path never goes into the shared lock
table, so for such a lock there is nothing for the local entry to
remember. Weak relation locks, AccessShareLock among them,
normally take that path, so asking about one was enough to crash.
The only in-core caller asks about AccessExclusiveLock, which
never uses the fast path, so nothing in the tree reaches this
today, but the autoprewarm worker proposed separately does.

Moving the lock into the shared lock table before looking would
avoid the crash, but it is a bad idea for a read-only check to
have the side effect of adding entries there, and it is also
unnecessary. A fast-path lock can only acquire a waiter once some
other backend requests a conflicting lock, and such a request
moves every matching fast-path lock into the shared lock table
before it waits. So if nobody has moved our lock, it has no
waiters.

Fix this by searching the shared lock table ourselves in that
case, and reporting no waiters unless we find both the lock and
our own proclock there, without moving anything either way.
Finding the lock alone does not mean ours was moved, because
another backend can have entered the same relation there for a
mode that moves nothing, such as ShareUpdateExclusiveLock.

Reported-by: Satyanarayana Narlapuram <[email protected]>
Author: Bharath Rupireddy <[email protected]>
Co-authored-by: Satyanarayana Narlapuram <[email protected]>
Reviewed-by: Robert Haas <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Palak Chaturvedi <[email protected]>
Discussion: https://postgr.es/m/CAHg+QDe_=ZahnRx37bzrqYenKn_S5YDQ00fTfwe-ZUmjqO=qLg@mail.gmail.com
---
 src/backend/storage/lmgr/lock.c | 50 ++++++++++++++++++++++++++++++---
 1 file changed, 46 insertions(+), 4 deletions(-)

diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 00978168bbf..a316ca35452 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -771,13 +771,55 @@ LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
 	LWLockAcquire(partitionLock, LW_SHARED);
 
 	/*
-	 * We don't need to re-find the lock or proclock, since we kept their
-	 * addresses in the locallock table, and they couldn't have been removed
-	 * while we were holding a lock on them.
+	 * Normally we don't need to re-find the lock or proclock, since we kept
+	 * their addresses in the locallock table. But those addresses are NULL if
+	 * we acquired the lock via the fast path, since then the lock was never
+	 * entered in the shared lock table. Such a lock can only acquire a waiter
+	 * if another backend requests a conflicting strong lock, and that request
+	 * first moves all matching fast-path locks into the shared table (see
+	 * FastPathTransferRelationLocks()). So we look for our own proclock, and
+	 * if there is none, our lock is still in our own fast-path array and
+	 * cannot have any waiters.
+	 *
+	 * Note that finding the lock is not proof that ours was transferred,
+	 * since the lock can also be there for a mode that transfers nothing,
+	 * such as another backend's ShareUpdateExclusiveLock. Hence we must find
+	 * both objects. We don't store them back into the locallock, since this
+	 * is a read-only check and LockRelease() does its own lookup anyway.
 	 */
 	lock = locallock->lock;
-	LOCK_PRINT("LockHasWaiters: found", lock, lockmode);
 	proclock = locallock->proclock;
+	if (!lock)
+	{
+		PROCLOCKTAG proclocktag;
+
+		Assert(EligibleForRelationFastPath(locktag, lockmode));
+		lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,
+													locktag,
+													locallock->hashcode,
+													HASH_FIND,
+													NULL);
+		if (!lock)
+		{
+			/* Still fast-path only, so nobody could be waiting on it. */
+			LWLockRelease(partitionLock);
+			return false;
+		}
+
+		proclocktag.myLock = lock;
+		proclocktag.myProc = MyProc;
+		proclock = (PROCLOCK *) hash_search(LockMethodProcLockHash,
+											&proclocktag,
+											HASH_FIND,
+											NULL);
+		if (!proclock)
+		{
+			/* Our lock wasn't transferred, so it has no waiters. */
+			LWLockRelease(partitionLock);
+			return false;
+		}
+	}
+	LOCK_PRINT("LockHasWaiters: found", lock, lockmode);
 	PROCLOCK_PRINT("LockHasWaiters: found", proclock);
 
 	/*
-- 
2.47.3

From bc04eeb587b8e5bbfbdf76a26f3dc99b2a9dc783 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 13 Sep 2026 20:11:27 +0000
Subject: [PATCH v4] Make autoprewarm yield to conflicting lock requests.

The autoprewarm worker opens every relation it has recorded
blocks for and holds AccessShareLock on it until all of those
blocks have been read back into shared buffers. On a large
relation that takes a long time, and anything that needs a
conflicting lock in the meantime, TRUNCATE or DROP for instance,
waits behind the worker for the whole of it. Nothing bounds that
wait, and it buys nothing, since prewarming is best-effort work
done for the benefit of later queries.

Fix this by having the worker check from time to time whether a
conflicting request is waiting on the relation, and give the
relation up when one is. It closes the relation, which releases
the lock, and moves on to the next one rather than coming back to
finish later, because once a conflicting strong lock has been
taken the blocks recorded for that relation may no longer be the
ones worth reading. Heap truncation in VACUUM already yields to
waiters the same way. The check is throttled by both a block
count and a time interval, so it costs nothing measurable while
nobody is waiting.

Note that the pg_prewarm() function is left as it is. Its caller
asked for a bounded amount of work and is waiting on the result,
which is the same reason a foreground VACUUM does not
deprioritize itself.

Author: Bharath Rupireddy <[email protected]>
Co-authored-by: Satyanarayana Narlapuram <[email protected]>
Reviewed-by: Robert Haas <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Palak Chaturvedi <[email protected]>
Discussion: https://postgr.es/m/CAHg+QDfdoR=7iqEAvLW9qtzV0Sx1wp2FuALeamqcCdiVEmMF-Q@mail.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

From a6c58de99a09257ad59c9590b10b6b12ef249410 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <[email protected]>
Date: Sun, 13 Sep 2026 20:11:27 +0000
Subject: [PATCH v4] Add test for autoprewarm yielding to conflicting lock
 requests.

The previous commit made the autoprewarm worker give up a
relation when a conflicting lock request is waiting, but nothing
exercises that path. Catching it by hand needs precise timing,
because the worker has to be caught while it still holds the lock
and still has blocks left to read.

This commit adds an injection point in the worker's block-read
loop and a TAP test that uses it. The test pauses the worker
while it is prewarming a table, starts a TRUNCATE that blocks
behind the worker's lock, then resumes the worker and checks that
the TRUNCATE completes and that the worker reports fewer
prewarmed blocks than it had dumped. The injection point carries
the relation name so that the test can wait for its own table
rather than for whichever relation reaches the check first, which
on a fresh cluster is a catalog.

The test then does the reverse, to pin down the fast-path case
the first commit fixed. A session takes ShareUpdateExclusiveLock
on the table the worker is prewarming. That does not conflict
with the worker and is granted at once, but it does enter the
relation in the shared lock table without moving the worker's own
lock there. The test checks both of those states through
pg_locks, then lets the worker go and checks that it prewarmed
the relation to the end without hitting the failure the first
commit fixed.

Note that the worker loads the dump only at startup, and
injection points do not survive a restart, so the wait can only
be armed after the restart. Keeping the worker busy long enough
for that takes a table of a few hundred megabytes and a buffer
pool large enough to hold it, which makes this test heavier than
one meant for the buildfarm.

Author: Bharath Rupireddy <[email protected]>
Reviewed-by: Palak Chaturvedi <[email protected]>
Discussion: https://postgr.es/m/CAHg+QDfdoR=7iqEAvLW9qtzV0Sx1wp2FuALeamqcCdiVEmMF-Q@mail.gmail.com
---
 contrib/pg_prewarm/Makefile                   |   3 +
 contrib/pg_prewarm/autoprewarm.c              |   9 +
 contrib/pg_prewarm/meson.build                |   4 +
 .../t/002_autoprewarm_lock_yield.pl           | 174 ++++++++++++++++++
 4 files changed, 190 insertions(+)
 create mode 100644 contrib/pg_prewarm/t/002_autoprewarm_lock_yield.pl

diff --git a/contrib/pg_prewarm/Makefile b/contrib/pg_prewarm/Makefile
index 617ac8e09b2..53bce44971a 100644
--- a/contrib/pg_prewarm/Makefile
+++ b/contrib/pg_prewarm/Makefile
@@ -12,6 +12,9 @@ PGFILEDESC = "pg_prewarm - preload relation data into system buffer cache"
 
 REGRESS = pg_prewarm
 
+EXTRA_INSTALL = src/test/modules/injection_points
+export enable_injection_points
+
 TAP_TESTS = 1
 
 ifdef USE_PGXS
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 0e909e34be2..660c1875298 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -47,6 +47,7 @@
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
+#include "utils/injection_point.h"
 #include "utils/rel.h"
 #include "utils/relfilenumbermap.h"
 #include "utils/timestamp.h"
@@ -549,6 +550,14 @@ apw_prewarm_blocks(Relation rel, struct AutoPrewarmReadStreamData *p)
 
 			blocks_since_check = 0;
 
+			/*
+			 * Pass the relation name so a test can wait here for a specific
+			 * relation only, instead of the first one that reaches this
+			 * point.
+			 */
+			INJECTION_POINT("autoprewarm-before-lock-check",
+							RelationGetRelationName(rel));
+
 			INSTR_TIME_SET_CURRENT(currenttime);
 			elapsed = currenttime;
 			INSTR_TIME_SUBTRACT(elapsed, starttime);
diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build
index e70546a451b..e43b9b2e1b8 100644
--- a/contrib/pg_prewarm/meson.build
+++ b/contrib/pg_prewarm/meson.build
@@ -35,8 +35,12 @@ tests += {
     ],
   },
   'tap': {
+    'env': {
+      'enable_injection_points': get_option('injection_points') ? 'yes' : 'no',
+    },
     'tests': [
       't/001_basic.pl',
+      't/002_autoprewarm_lock_yield.pl',
     ],
   },
 }
diff --git a/contrib/pg_prewarm/t/002_autoprewarm_lock_yield.pl b/contrib/pg_prewarm/t/002_autoprewarm_lock_yield.pl
new file mode 100644
index 00000000000..d2c8c05b4e6
--- /dev/null
+++ b/contrib/pg_prewarm/t/002_autoprewarm_lock_yield.pl
@@ -0,0 +1,174 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test how the autoprewarm worker reacts to other lock requests on a relation
+# it is prewarming. It gives the relation up for a request that conflicts with
+# its AccessShareLock, and carries on for one that does not.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+
+# The worker loads the dump only at startup and injection points do not survive
+# a restart, so the wait can only be armed after a restart. The table must be
+# big enough that the worker is still reading it by then, and the buffer pool
+# big enough to hold it, since the worker stops prewarming once buffers run
+# short. Keep both well under 2GB so that NBuffers * BLCKSZ stays
+# representable on 32-bit platforms.
+$node->append_conf(
+	'postgresql.conf', qq{
+shared_preload_libraries = 'pg_prewarm,injection_points'
+pg_prewarm.autoprewarm = true
+pg_prewarm.autoprewarm_interval = 0
+autovacuum = off
+shared_buffers = '512MB'
+});
+$node->start;
+
+# The injection_points extension may not be installed under installcheck.
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', q(
+	CREATE EXTENSION pg_prewarm;
+	CREATE EXTENSION injection_points;
+));
+
+$node->safe_psql('postgres', q(
+	CREATE TABLE warm_tbl (id int, pad text);
+	INSERT INTO warm_tbl SELECT g, repeat('x', 500)
+		FROM generate_series(1, 500000) g;
+));
+
+# The table must exceed the worker's lock-check interval (in blocks) so that
+# the worker reaches the injection point while still scanning it.
+my $nblocks = $node->safe_psql('postgres',
+	"SELECT pg_relation_size('warm_tbl') / current_setting('block_size')::int");
+ok($nblocks > 32, "table has more than 32 blocks ($nblocks)");
+
+# Warm the table and record its blocks so the worker reloads them on restart.
+$node->safe_psql('postgres', "SELECT pg_prewarm('warm_tbl', 'buffer')");
+$node->safe_psql('postgres', "SELECT autoprewarm_dump_now()");
+
+$node->restart;
+
+# Pause the worker while it holds AccessShareLock on warm_tbl. The condition
+# matters: other relations in the dump reach this point first (some catalogs
+# have more than 32 blocks), and the worker would then be holding a lock on the
+# wrong relation, so the TRUNCATE below would not block.
+$node->safe_psql('postgres',
+	"SELECT injection_points_attach('autoprewarm-before-lock-check', 'wait', 'warm_tbl')");
+$node->wait_for_event('autoprewarm worker', 'autoprewarm-before-lock-check');
+
+# TRUNCATE now blocks on the AccessExclusiveLock the worker conflicts with.
+my $truncate = $node->background_psql('postgres');
+$truncate->query_until(qr/starting_truncate/, q(
+	\echo starting_truncate
+	TRUNCATE warm_tbl;
+));
+$node->poll_query_until('postgres', q(
+	SELECT count(*) > 0 FROM pg_stat_activity
+	WHERE query LIKE '%TRUNCATE warm_tbl%' AND wait_event_type = 'Lock';
+)) or die "timed out waiting for TRUNCATE to block on the lock";
+
+# Resume the worker; it should see the waiter and release its lock.
+my $log_offset = -s $node->logfile;
+$node->safe_psql('postgres',
+	"SELECT injection_points_detach('autoprewarm-before-lock-check')");
+$node->safe_psql('postgres',
+	"SELECT injection_points_wakeup('autoprewarm-before-lock-check')");
+
+$truncate->quit;
+pass('TRUNCATE completed while autoprewarm worker was prewarming');
+
+# Having given up the table, the worker warmed fewer blocks than it dumped.
+$node->wait_for_log(
+	qr/autoprewarm successfully prewarmed \d+ of \d+ previously-loaded blocks/,
+	$log_offset);
+my $summary = slurp_file($node->logfile, $log_offset);
+my ($prewarmed, $total) = $summary =~
+	/successfully prewarmed (\d+) of (\d+) previously-loaded blocks/;
+cmp_ok($prewarmed, '<', $total,
+	"worker gave up early: prewarmed $prewarmed of $total blocks");
+
+# Now a request that does not conflict, which must not make the worker give up.
+# The worker takes AccessShareLock through the fast path, so it has no proclock.
+# ShareUpdateExclusiveLock is too strong for the fast path, so it enters the
+# relation in the main lock table, but too weak to conflict with the worker, so
+# nothing moves the worker's lock there and it is granted at once, leaving no
+# waiter. The worker must find the relation in the main lock table, find no
+# proclock of its own, and prewarm the relation to the end. VACUUM, ANALYZE and
+# CREATE INDEX CONCURRENTLY all take this mode.
+#
+# The TRUNCATE above emptied the table, so fill and dump it again.
+$node->safe_psql('postgres', q(
+	INSERT INTO warm_tbl SELECT g, repeat('x', 500)
+		FROM generate_series(1, 500000) g;
+));
+$node->safe_psql('postgres', "SELECT pg_prewarm('warm_tbl', 'buffer')");
+$node->safe_psql('postgres', "SELECT autoprewarm_dump_now()");
+
+$node->restart;
+
+# Pause the worker again, this time to inspect the locks it holds.
+$node->safe_psql('postgres',
+	"SELECT injection_points_attach('autoprewarm-before-lock-check', 'wait', 'warm_tbl')");
+$node->wait_for_event('autoprewarm worker', 'autoprewarm-before-lock-check');
+
+is( $node->safe_psql(
+		'postgres', q(
+	SELECT count(*) FROM pg_locks
+	WHERE relation = 'warm_tbl'::regclass
+		AND mode = 'AccessShareLock' AND fastpath;
+)),
+	'1',
+	'worker holds AccessShareLock on the relation through the fast path');
+
+# Another session takes ShareUpdateExclusiveLock and holds it.
+my $holder = $node->background_psql('postgres');
+$holder->query_safe(
+	'BEGIN; LOCK TABLE warm_tbl IN SHARE UPDATE EXCLUSIVE MODE;');
+is( $node->safe_psql(
+		'postgres', q(
+	SELECT count(*) FROM pg_locks
+	WHERE relation = 'warm_tbl'::regclass
+		AND mode = 'ShareUpdateExclusiveLock' AND granted AND NOT fastpath;
+)),
+	'1',
+	'relation is in the main lock table for a mode that does not conflict');
+
+# Resume the worker; it must prewarm the relation to the end.
+$log_offset = -s $node->logfile;
+$node->safe_psql('postgres',
+	"SELECT injection_points_detach('autoprewarm-before-lock-check')");
+$node->safe_psql('postgres',
+	"SELECT injection_points_wakeup('autoprewarm-before-lock-check')");
+
+$node->wait_for_log(
+	qr/autoprewarm successfully prewarmed \d+ of \d+ previously-loaded blocks/,
+	$log_offset);
+$summary = slurp_file($node->logfile, $log_offset);
+unlike($summary, qr/failed to re-find shared proclock object/,
+	'worker did not fail looking for a proclock it never had');
+($prewarmed, $total) = $summary =~
+	/successfully prewarmed (\d+) of (\d+) previously-loaded blocks/;
+is($prewarmed, $total,
+	"worker kept the relation: prewarmed $prewarmed of $total blocks");
+
+$holder->query_safe('ROLLBACK');
+$holder->quit;
+
+$node->stop;
+done_testing();
-- 
2.47.3

Reply via email to