From 439f4af23264c180e3c3c0f217d57da469c5f46d Mon Sep 17 00:00:00 2001
From: Yuhang Qiu <iamqyh@gmail.com>
Date: Wed, 26 Aug 2026 11:57:21 +0000
Subject: [PATCH v3 1/4] read_stream: Allow changing the buffer access strategy

Heap scans can replace their buffer access strategy during a rescan.
A read stream retained across such a rescan would keep references to
the old strategy, which may then be freed.

Add read_stream_set_strategy() to update an idle stream. It changes the
strategy used by subsequent reads and recomputes the pin limit within the
existing allocation. Clamp current and saved look-ahead distances to the
new limit.

Reviewed-by: Nazir Bilal Yavuz <byavuz81@gmail.com>
Discussion: https://postgr.es/m/D1F99EB2-5A03-465F-A210-98F7DF256168@gmail.com
---
 src/backend/storage/aio/read_stream.c | 42 +++++++++++++++++++++++++++
 src/include/storage/read_stream.h     |  2 ++
 2 files changed, 44 insertions(+)

diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index e7dbbe03326..6bc5b000fb1 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -1407,6 +1407,48 @@ read_stream_resume(ReadStream *stream)
 	stream->combine_distance = stream->resume_combine_distance;
 }
 
+/*
+ * Change the buffer access strategy used for reads from this stream, or pass
+ * NULL to stop using one.
+ *
+ * The stream must be idle.  Buffers acquired under the old strategy are
+ * tracked in its ring, and in-progress IOs still refer to it, so callers that
+ * need to abandon a strategy on a busy stream use read_stream_clear_strategy()
+ * instead.
+ *
+ * The pin limit was derived from the strategy when the stream was created, so
+ * recompute it here.  The single allocation made at that point cannot grow, so
+ * the new limit stays capped by what was allocated.
+ */
+void
+read_stream_set_strategy(ReadStream *stream, BufferAccessStrategy strategy)
+{
+	Assert(stream->pinned_buffers == 0);
+	Assert(stream->ios_in_progress == 0);
+	Assert(stream->pending_read_nblocks == 0);
+
+	for (int i = 0; i < stream->max_ios; ++i)
+		stream->ios[i].op.strategy = strategy;
+
+	stream->max_pinned_buffers = Max(1,
+									 Min(stream->queue_size - 1,
+										 GetAccessStrategyPinLimit(strategy)));
+
+	/*
+	 * The look-ahead distances are only clamped as they grow, so a limit that
+	 * just shrank has to be applied to them here, including the distances
+	 * saved for resuming a paused stream.
+	 */
+	stream->readahead_distance = Min(stream->readahead_distance,
+									 stream->max_pinned_buffers);
+	stream->combine_distance = Min(stream->combine_distance,
+								   stream->max_pinned_buffers);
+	stream->resume_readahead_distance = Min(stream->resume_readahead_distance,
+											stream->max_pinned_buffers);
+	stream->resume_combine_distance = Min(stream->resume_combine_distance,
+										  stream->max_pinned_buffers);
+}
+
 /*
  * Stop using a buffer access strategy for reads from this stream.
  *
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index e2dcf1be50a..feac23c9b51 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -102,6 +102,8 @@ 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_set_strategy(ReadStream *stream,
+									 BufferAccessStrategy strategy);
 extern void read_stream_clear_strategy(ReadStream *stream);
 extern void read_stream_reset(ReadStream *stream);
 extern void read_stream_end(ReadStream *stream);
-- 
2.43.7

