From fbf889de95606471cecf4ff5533dc24d40cb8700 Mon Sep 17 00:00:00 2001 From: Peter Geoghegan Date: Wed, 19 Aug 2026 12:30:56 -0400 Subject: [PATCH] Fix GIN VACUUM posting tree root split bug. ginVacuumPostingTreeLeaves swaps a shared buffer lock for an exclusive one when it encounters a leaf page. It neglected to re-verify whether a page that was initially a leaf root page became an internal page due to a concurrent root page split (during the window when no lock was held). It was therefore possible for GIN VACUUM to spuriously treat an internal page as a leaf page, leading to data corruption. VACUUM could miss dead TIDs that it was required to remove, leaving behind dangling references in the index. To fix, re-verify that a leaf page is still a leaf page after an exclusive lock is acquired. If it isn't, drop our exclusive lock and acquire a shared lock so that the non-leaf root page gets processed in the usual way. Oversight in commit fd83c83d, which fixed a deadlock bug in GIN posting tree vacuuming. Author: Peter Geoghegan Reviewed-by: Andrey Borodin Discussion: https://postgr.es/m/CAH2-Wz=RBpJTQgvOxr6C=J04dExmFSt1E3F-r+cRTQ56hEotkg@mail.gmail.com Backpatch-through: 14 --- src/backend/access/gin/ginvacuum.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index ef7b71057ef..6665ce0fec7 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -366,6 +366,16 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno) { LockBuffer(buffer, GIN_UNLOCK); LockBuffer(buffer, GIN_EXCLUSIVE); + + if (!GinPageIsLeaf(page)) + { + /* + * The root page was a leaf page, but became an internal page + * while no lock was held. Unlock and reacquire a share lock. + */ + UnlockReleaseBuffer(buffer); + continue; + } break; }