From 7f91e3d66a924c25d94bca4b39527faf7c067b91 Mon Sep 17 00:00:00 2001
From: Kevin Rocker <me@kevinrocker.com>
Date: Mon, 28 Sep 2026 02:32:16 +0200
Subject: [PATCH v8 2/3] Move remaining interrupt checks out of locked regions.

vacuum_delay_point() and CHECK_FOR_INTERRUPTS() cannot process pending
interrupts while interrupts are held off.

Move the first GIN pending-list cleanup delay point before its locks
are acquired; an existing delay point already covers transitions
between pages.

Move the hash bucket cleanup delay point from hashbucketcleanup() up to
hashbulkdelete()'s per-bucket loop, before the bucket's cleanup lock is
acquired.  hashbucketcleanup() is called assuming a lock exists for
its duration, so no part of it is a valid call site.  Its other callers,
the split-cleanup paths called from insertion, lose the call entirely.
A backend running INSERT doesn't do vacuum cost accounting and there's
an active lock, so the call couldn't sleep there anyway.

dshash sequential iteration returns each stats entry with its
partition lock held and provides no unlocked per-entry boundary, so
mark that call with a grep-friendly comment instead.

Author: Kevin Rocker <me@kevinrocker.com>
Author: Andrey Borodin <amborodin@acm.org>
Reviewed-by: Neil Chen <carpenter.nail.cz@gmail.com>
Discussion: https://postgr.es/m/492c6247-43d3-477b-8981-fb0c56767b38%40app.fastmail.com
---
 src/backend/access/gin/ginfast.c    | 5 +++--
 src/backend/access/hash/hash.c      | 5 +++--
 src/backend/utils/activity/pgstat.c | 4 ++++
 3 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 46fc60115a8..bb678300b1b 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -797,6 +797,9 @@ ginInsertCleanup(GinState *ginstate, bool must_empty_list,
 	bool		fsm_vac = false;
 	int			workMemory;
 
+	/* Delay or accept interrupts before acquiring the pending-list locks. */
+	vacuum_delay_point(false);
+
 	/*
 	 * We would like to prevent concurrent cleanup process. For that we will
 	 * lock metapage in exclusive mode using LockPage() call. Nobody other
@@ -895,8 +898,6 @@ ginInsertCleanup(GinState *ginstate, bool must_empty_list,
 		 */
 		processPendingPage(&accum, &datums, page, FirstOffsetNumber);
 
-		vacuum_delay_point(false);
-
 		/*
 		 * Is it time to flush memory to disk?	Flush if we are at the end of
 		 * the pending list, or if we have a full row and memory is getting
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index b2e34d2d45e..3d48355eb08 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -562,6 +562,9 @@ bucket_loop:
 		Page		page;
 		bool		split_cleanup = false;
 
+		/* Delay or accept interrupts before locking the next bucket. */
+		vacuum_delay_point(false);
+
 		/* Get address of bucket's start page */
 		bucket_blkno = BUCKET_TO_BLKNO(cachedmetap, cur_bucket);
 
@@ -799,8 +802,6 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		bool		retain_pin = false;
 		bool		clear_dead_marking = false;
 
-		vacuum_delay_point(false);
-
 		page = BufferGetPage(buf);
 		opaque = HashPageGetOpaque(page);
 
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 6dd13ab9dec..cef49b8a98f 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -1745,6 +1745,10 @@ pgstat_write_statsfile(void)
 		PgStatShared_Common *shstats;
 		const PgStat_KindInfo *kind_info = NULL;
 
+		/*
+		 * CHECK_FOR_INTERRUPTS_WITH_INTERRUPTS_HELD: dshash_seq_next()
+		 * returns with the current hash partition lock still held.
+		 */
 		CHECK_FOR_INTERRUPTS();
 
 		/*
-- 
2.54.0

