Hi,

ginVacuumPostingTreeLeaves() sweeps the leaf pages of a GIN posting tree
via their rightlinks, but that loop has no vacuum_delay_point() (and hence
no CHECK_FOR_INTERRUPTS()), unlike the sibling loops in ginbulkdelete() and
ginvacuumcleanup().

The call used to be there: it was dropped when fd83c83d094 rewrote this
cleanup from a recursive walk into the current iterative sweep, and never
re-added to the new loop.  That commit was only about fixing a deadlock, so
the removal looks inadvertent.

A posting tree holds all TIDs for a single key, so for a common key it can
span many leaf pages.  While one such tree is being vacuumed, the operation
now ignores vacuum_cost_delay and cannot be interrupted (query cancel,
statement_timeout, or autovacuum cancellation when another backend wants a
conflicting lock) until the whole sweep finishes; a corrupted rightlink
forming a cycle turns it into an uninterruptible hang.

The attached patch restores the call, placed where the buffer has already
been released so no content lock is held across the delay.  It is against
master; on branches before 18 vacuum_delay_point() takes no argument (the
is_analyze parameter was added by e5b0b0ce150), so the back-branch version
is just "vacuum_delay_point();".

I left the initial leftmost-descent loop in the same function as-is, since
it mirrors the entry-tree descent in ginbulkdelete(), which likewise has no
delay point.

Thanks,
Paul
From d9b5ddb3cb0cfb55d418a09f571a78ee069eb3e1 Mon Sep 17 00:00:00 2001
From: Paul Kim <[email protected]>
Date: Sun, 19 Jul 2026 23:08:12 +0900
Subject: [PATCH v1] Restore vacuum_delay_point() in GIN posting-tree leaf
 vacuum

Commit fd83c83d094 turned the recursive posting-tree cleanup in
ginVacuumPostingTreeLeaves() into an iterative sweep that follows the
tree's leaf pages via their rightlinks.  The recursive version called
vacuum_delay_point() while processing the tree, but that call was removed
and never re-added to the new loop.  As that commit only set out to fix a
deadlock, the removal appears to have been unintentional.

Consequently the leaf-page sweep of a single posting tree runs with no
vacuum_delay_point(), and therefore no CHECK_FOR_INTERRUPTS().  A posting
tree stores all the TIDs for one indexed key, so for a frequently-occurring
key it can span a large number of leaf pages.  While such a tree is being
vacuumed the operation ignores vacuum_cost_delay and does not respond to
query cancellation or statement_timeout; an autovacuum worker likewise
cannot be interrupted mid-sweep when another backend requests a conflicting
lock.  A corrupted rightlink that forms a cycle makes the loop run
uninterruptibly forever.

Restore the call, placed after the current page has been unlocked and
released so that no buffer content lock is held across a potential delay
(cf. 21c27af65fb).  The sibling loops in ginbulkdelete() and
ginvacuumcleanup() already call vacuum_delay_point() once per page.

Back-patch to all supported branches, which all contain fd83c83d094.
---
 src/backend/access/gin/ginvacuum.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 840543eb664..a0b81a215a9 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -429,6 +429,8 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
 		if (blkno == InvalidBlockNumber)
 			break;
 
+		vacuum_delay_point(false);
+
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
 									RBM_NORMAL, gvs->strategy);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
-- 
2.50.1 (Apple Git-155)

Reply via email to