From 48512d279c820192959a1599b48784c8db40623c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Mon, 3 Aug 2026 17:06:30 -0400
Subject: [PATCH 2/2] Fix VACUUM failsafe mode's dropping of the buffer access
 strategy

Since 4830f1024325, when VACUUM's wraparound failsafe triggers, it stops
using the BAS_VACUUM buffer access strategy so that the rest of the
vacuum may use all of shared buffers and reclaim transaction IDs as
quickly as possible.

However, when 9256822608f3 made vacuum's first phase use the read stream,
it accidentally disabled this functionality. To re-enable it, we have to
clear the strategy actually being used by the read stream.
---
 src/backend/access/heap/vacuumlazy.c  | 19 ++++++++++++++++---
 src/backend/storage/aio/read_stream.c | 13 +++++++++++++
 src/include/storage/read_stream.h     |  1 +
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 39395aed0d5..2dc9a9de6a9 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -1385,6 +1385,17 @@ lazy_scan_heap(LVRelState *vacrel)
 										 PROGRESS_VACUUM_PHASE_SCAN_HEAP);
 		}
 
+		/*
+		 * If the wraparound failsafe has engaged -- either via the check
+		 * above or during index vacuuming invoked from this loop -- stop
+		 * using the buffer access strategy. Currently in-progress reads are
+		 * not affected. This only affects phase I vacuum scan of the heap, so
+		 * we cannot clear the read stream strategy in
+		 * lazy_check_wraparound_failsafe().
+		 */
+		if (unlikely(VacuumFailsafeActive))
+			read_stream_clear_strategy(stream);
+
 		buf = read_stream_next_buffer(stream, &per_buffer_data);
 
 		/* The relation is exhausted. */
@@ -2905,9 +2916,11 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
 		VacuumFailsafeActive = true;
 
 		/*
-		 * Abandon use of a buffer access strategy to allow use of all of
-		 * shared buffers.  We assume the caller who allocated the memory for
-		 * the BufferAccessStrategy will free it.
+		 * Clear this just for tidiness. An ongoing phase I heap scan already
+		 * has its own copy of the strategy (which it will clear itself) and
+		 * none of the other vacuum phases will read from the strategy member
+		 * once failsafe mode is engaged. We assume the caller who allocated
+		 * memory for the BufferAccessStrategy will free it.
 		 */
 		vacrel->bstrategy = NULL;
 
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index 03dd7fee0ae..57b107044a9 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -1410,6 +1410,19 @@ read_stream_resume(ReadStream *stream)
 	stream->combine_distance = stream->resume_combine_distance;
 }
 
+/*
+ * Stop using a buffer access strategy for future reads from this stream.
+ *
+ * This does not change whether or not any in-progress IOs are using the
+ * buffer access strategy. Note that the caller is still responsible for
+ * freeing the memory.
+ */
+void
+read_stream_clear_strategy(ReadStream *stream)
+{
+	stream->strategy = NULL;
+}
+
 /*
  * Reset a read stream by releasing any queued up buffers, allowing the stream
  * to be used again for different blocks.  This can be used to clear an
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index 48995c6d534..e2dcf1be50a 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -102,6 +102,7 @@ extern ReadStream *read_stream_begin_smgr_relation(int flags,
 												   size_t per_buffer_data_size);
 extern BlockNumber read_stream_pause(ReadStream *stream);
 extern void read_stream_resume(ReadStream *stream);
+extern void read_stream_clear_strategy(ReadStream *stream);
 extern void read_stream_reset(ReadStream *stream);
 extern void read_stream_end(ReadStream *stream);
 extern void read_stream_enable_stats(ReadStream *stream, struct IOStats *stats);
-- 
2.47.3

