From dff250e499f993f8e5ac7a2e6bd2bbf6b38fd153 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Wed, 22 Jul 2026 22:28:01 +0300 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. Reported-by: Paul Kim Discussion: https://postgr.es/m/178447127453.110.12276981925360691905%40mail.gmail.com Backpatch-through: 14 --- src/backend/access/gin/ginvacuum.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 840543eb664..040f21a92e3 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -429,6 +429,13 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno) if (blkno == InvalidBlockNumber) break; + /* + * A safe point to delay/accept interrupts: the previous page has been + * unlocked and released, so we hold no buffer content lock (nor any + * other LWLock) here and CHECK_FOR_INTERRUPTS() can do its job. + */ + 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)